How it works
The model proposes.
SQAI disposes.
An agent never writes SQL and never emits code to run. It writes a typed request — and every request travels the same line: validated against a pinned contract, checked against your policy, executed on a deterministic engine, returned with the hashes that replay it.
Every request — ask(), compute(), or an AI SDK tool call — runs the same line. Nothing touches the engine until every check has passed.
01Typed intent
A request the engine can parse. Not a string it must trust.
The model emits a typed object — never SQL, never code. A query intent is a QuerySpec: a metric to aggregate over a named source. A computation intent is a ComputationSpec: a named function from the contract, with arguments and bindings. One discriminated union, two shapes — and nowhere in either for an executable string to hide.
That is the entire authoring surface. What the shape cannot express, the model cannot ask for.
QuerySpecanatomy{"kind": "query",the union tag — query or computation"version": "1",the spec version, pinned"source_name": "sales",a connected source, by exact name"metric": "revenue",the column to aggregate"aggregation": "sum",one of sum · avg · count · min · max"group_by": "region"optional — split the result by a field}also optionalfilter · limit · order
the computation shapemodule · function · args · kwargs · bindings · seed?
02Contract check
One contract, hash-pinned. The unknown is refused with directions.
The spec is validated against the capability contract — one generated, hash-pinned file listing every operation, its exact signature, and its determinism flags. A capability is reachable only if it is read-only and deterministic, or deterministic when seeded. Write-capable and non-deterministic operations are not blocked at runtime; they were never generated into the surface at all.
An unknown function fails as unsupported_operation and answers with nearest_matches from the same index — the model corrects itself instead of looping.
contract_hashsha256:79f1c5a6c7164e7e9e1750e70a5c03292fa87eb52d8148a740c06695924be9a1
- 4,778
- operations listed in the contract
- 4,574
- exposed to the SDKs, read-only
- 4,564
- fully deterministic
- 10
- seed-required simulations
- 204
- excluded from the surface
eligibilityread_only && (deterministic || deterministic_when_seeded)
on an unknown nameunsupported_operation + nearest_matches
03Policy check
Your allow-list decides whether — and how — it runs.
Policy is fixed when createSQAI() constructs the instance and enforced in-process before anything executes — on the source, on every metric, group, filter, and bound column, and on the function name. It can only narrow the contract: naming a capability outside the eligible surface still throws unsupported_operation.
The model's tool input carries no allowed* field. Nothing in the request can widen access — so there is nothing for a prompt injection to widen.
allowedSourcessources the model may name
- "sales"
allowedFieldscolumns it may read, per source
- sales.region
- sales.revenue
- sales.order_date
allowedFunctionsthe default — every read-only capability, nothing more
- "all-readonly"
a denial is precise, attributed, final
- policy_denied_source
- policy_denied_field
- policy_denied_function
source: "sqai" · non-retryable
04Deterministic engine
Pinned before it runs: float64, one thread, one runtime.
First, bindings resolve. The model named a source and a field; SQAI pulls the actual values through the engine's row-aligned extractColumns primitive, nulls handled pairwise. SQAI never zips arrays itself — so the input that gets hashed is exactly the input that executed.
Only now does the request touch the engine. The runtime is signed, versioned, and pinned: float64 precision, a single thread. The first compute() ever provisions it once, in about 110 seconds; after that it stays resident — finance.npv measures 0.83–0.93 ms warm.
0.1.04d64142e4c1ff63d299cfc8b172fdf97cb59169b545e1e02978e678a632ce6e1darwinarm64float641—2ea5ede72acd2912fe9e1230cef34afad4c9436bfa8a709bac2da02b478e3212Recorded with every result — the envelope states the scope of the guarantee instead of overclaiming it.
05Answer + provenance
Two hashes. One before, one after.
invocation_hash is stamped before execution — over the module, function, arguments, resolved bindings, seed, contract, and scope. computation_hash is stamped after: the invocation hash folded with the canonical result. Replay means run it again and compare bytes.
Both SDKs share one canonical serializer, so the same computation yields byte-identical hashes in TypeScript and Python. Query answers carry the same discipline: a plan_hash over the canonical resolved plan, plus the decision path and the exact scope it ran in.
3188.1687606249325finance.npv · bound to a live column · 0.83 ms warm
stamped before execution
invocation_hash
8223a694250fc751fcf8a7777b8e1f524c44ff1f0e7e83451467f554a6123950
module · function · args · resolved bindings · seed · contract · scope
stamped after execution
computation_hash
ff5280ea128113f528dd1e9a28b9bc4a81469075ed7c981c7176fb172d98085d
the invocation hash, folded with the canonical result
Byte-identical in TypeScript and Python — a result computed in one language replays and verifies in the other.
both computed againstcontract_hash sha256:79f1c5a6…
on the query planeplan_hash f87610d8afeb…decision_path "exact_spec"deterministic_scope "local_registered_source"
06The discovery loop
Never guess. Discover, dry-run, then spend the execution.
Field names and function signatures are discovered, not hallucinated. The AI SDK exposes exactly three tools — and only one of them executes.
- 01
listSources()What can I touch?
Connected sources, with exact field names, types, and the allowed operations per field. Never executes.
- 02
listSources({ capabilitySearch: "npv" })What can I compute?
The same tool, searching the deterministic catalog — matching modules, exact signatures, and whether a seed is required.
- 03
explainQuery({ … })Will it run?
A dry run. Returns the resolved plan, confidence, and a preview invocation_hash — a preview, never an executed hash.
- 04
queryData({ … })Run it.
The one tool that executes. Returns the result plus its full provenance; oversized results stay retrievable by result_id.
The tools never throw — every outcome is typedok · needs_clarification · rejected · error
The mechanics, field by field.
Read the docs →- Deterministic AIthe envelope and the three hashes, field by field
- AI data governanceallow-lists and the narrow-only rule, at depth
- Connect dataevery source lands as the same typed shape