What's new
What changed in each Aito release, from your side: new capabilities, improvements and fixes. Written per release โ not a commit log.
Which build am I on? GET /version on your instance reports the build it
is running. Docs for a specific release live at /docs/api/<version>/; this
page and /docs/api/ describe the current release, and /docs/api/edge/
tracks development ahead of it โ so a feature listed here is available on the
current release unless an entry says otherwise.
v2.9.2
Two query bugs and two speedups on the plain database surface. A range
filter now answers on the v1 engine โ it used to return a server error โ
and relate on a text column counts the rows it says it counts. The
capacity and head-to-head benchmark figures are re-measured.
Fixed
- A range filter on the v1 engine returned a server error. A two-sided
window โ
{"id": {"$gt": 51200, "$lt": 52200}}, a SQLBETWEEN, or any two comparisons on the same column โ failed with an internalbit & operation requires same sized bit setsinstead of answering. One-sided comparisons were always correct, and the v2 engine was never affected. See the query reference. relateand$patternson a Text column reported the wrong support. A mined rule shown as one whole value was counted over every row sharing its words, so a rule displayed as a company with 245 invoices could report 331 โ the rows of that company and of every other name containing the same words. Text conditions now replay to exactly the rows their support counted. See the query reference.
New
- Choose which index of a text column you relate โ
.$distinctand.$token. A Text column carries both its distinct whole values and its analyzed tokens;relate,$patternsand$propscan now name either (product.name.$distinct,product.name.$token).$patternsoffers both pools by default and ranks them together, so a specific whole-value rule is available where it is the better one. Asking a column for an index it does not have is refused by name. See the query reference.
Improved
- Range and comparison filters no longer cost more as the table grows.
$lt$lte$gt$gteon numbers, strings and timestamps, plus$startsWith,$modand the timestamp component operators, allocated a bitset per matching value โ so a comparison keeping many of a column's distinct values grew quadratically with the table. At 102 400 rows a 1 000-wide window went from 378 ms to 20 ms. - Relational aggregates resolve their fields once per query.
select: ["$count", "$avg:amount", "$min:id", "$max:id"]under a filter went from 111 ms to 2.5 ms at 102 400 rows on the v2 engine. See the query reference.
Changed
- The capacity page reports the memory a server process uses, not the benchmark harness's. The old figures were read from a JVM that also held the test corpus, and were wrong in both directions. At 10 000 000 rows v2 holds the database in 213 MB of heap (published: 1 720 MB) and v1 in 5 290 MB (published: 2 603 MB) โ so a 10M expense database does not fit a v1 engine in a 4 GB heap. See capacity.
- The Elasticsearch head-to-head is re-measured. The previous table timed
one query shape at a time, so the first shape measured absorbed the whole
stack's warm-up and the last looked fastest โ which is why it showed
filter+textbeating both of its own constituents. The shapes are now interleaved on an idle machine. See performance.
v2.9.1
No engine changes. The scaling figures are re-measured on an optimized database โ the configuration a deployment actually runs โ and now lead with the warm median rather than a mean that averaged the first requests after a restart into steady state.
Changed
- The scaling tables measure an optimized database, and lead with warm latency. Two things moved together. The measurement now runs against an optimized database rather than an as-ingested one, and each row leads with the warm median, with the warm p90, the mean including the cold start, and the first query beside it. At 10 000 000 rows v1 reads about 210 ms at the warm median (417 ms p90, 355 ms mean including the cold start) and v2 about 279 ms (823 ms, 601 ms). The v2.9.0 page reported the as-ingested database with the mean: 853 ms median and 1 638 ms mean for v1 at the same scale. Those as-ingested figures stay on the page as the comparison, so the cost of leaving a database uncompacted is still visible. The engine is unchanged from v2.9.0. See scaling.
- The capacity page says plainly that an as-ingested database answers more slowly than an optimized one, and points at the warm-up breakdown. See capacity.
v2.9.0
The v2 API is in beta. Recommendations now use their query and your
basedOn in earnest, and relate and _evaluate reach per member and per
word. One change needs action: in a filter, a bare value on a Text column
now matches the whole value exactly โ use $match to filter by words.
Changed
- A bare value on a Text column filters by the exact value. In a filter โ
_query,_search,_aggregate, a SQL=, a delete โ{"name": "BASF Oy"}now selects the rows whosenameis exactlyBASF Oy(case-sensitive), matching v1. It used to match the words in any order, so it also returned โ and a delete also removed โ "BASF Construction Chemicals Finland Oy". To filter by words, use$match. As evidence in_predict,_recommendor_match, or when ordering by$p, a bare Text value still counts word by word. See the query reference.
New
relatea linked text field word by word.relate: ["product.name.$feature"]returns one relation per word of the linked product's name, as that column's analyzer produces it. See the query reference._evaluatemeasures per-member predictions and combined test selectors.predict: "tags.$feature"evaluates a multi-label prediction member by member, and atestselector can combine$indexwith a field condition. See evaluation.- Two public benchmarks, each with its data, its baselines and its numbers: smart search & recommendations and invoice line โ product matching.
Improved
- Recommendations use their query and
basedOn._recommendnow scores every candidate against its context exactly, where most candidates used to be ranked by how likely they were to meet the goal regardless of the query. On our smart-search benchmark, a recommendation from query words alone went from nDCG@10 0.12 โ about random โ to 0.45, level with BM25 over the catalogue, and reaches 0.53โ0.55 withbasedOn. A For You shelf ranked from the shopper's profile went from 0.11 to 0.18, and 0.24 withbasedOn. Rankings will differ from v2.8.4. - A text where through a link resolves a new word in about half a millisecond, where it took about 180 ms.
- A v1 match against a link target of over 100 000 rows answers in about 2 s instead of about 12 s.
Fixed
- A link-target prediction scoped by an attribute of the target honours that scope again. A regression in v2.8.4.
basedOnapplies to_recommend. v2.8.4 accepted it and ignored it.relateon a set field through a link relates each member, as it does on the field itself, instead of each whole set.- A text where through a link counts each word as evidence, as the same where on the table's own text column does.
- Statistics caches no longer serve a result computed for different data.
Two caches were keyed on only part of what they read, so a query could answer
200with statistics from other data; they now key on all of it.
Also in this release: an experimental x-knowledge column type and its
$surprisal operator. Experimental surfaces carry no compatibility promise and
may change between releases โ see schema design.
v2.8.4
The v2 API is in beta. A latency-and-correctness release: the largest single
cost of a scoped link-target prediction is gone, a linked column now supports
every operator its direct counterpart does, and six ways a query could return
the wrong rows or candidates with a 200 are fixed.
New
- Every where-operator works through a link.
$gt/$gte/$lt/$lte,$mod,$numeric,$startsWithand$searchapply to a field reached by its dotted path ({"part.price": {"$gte": 10}}) exactly as to the column itself โ the operator vocabulary is a property of the field's type, not of how you reached it. A bare value,$oror$inagainst a linked Set/Array column is membership, as on the column, and a column value operation selects through the link ("select": ["part.desc.$tokenCount"]). See fields through a link.
Improved
- A scoped link-target prediction answers in a fraction of the time. When a
prediction is scoped by a where on a linked path (
invoice.customer: X), the engine walked every candidate value of the link โ every seen and every unseen target row โ to test which ones the scope admits; it now derives the candidates from the linked rows the scope admits. The candidate universe of a link target is also built once per database state rather than once per request, and the per-candidate evidence selection does its grouping once. On a 16 000-candidate scope a fresh request went from about 0.8 s to about 0.6 s; on a few-thousand-candidate scope it answers in about 0.3 s. This particular change does not affect the answers. See improving inference. - Matching is faster and more accurate. When ranking candidates, the engine
built a pool of relations twice the size it could keep, refined all of them,
and then discarded the surplus. It now refines only what it keeps. On our ERP
matching benchmark the realistic
basedOnarms gain up to ten points of top-1 accuracy (for example 62.5% to 72.5% matching on name and supplier, mean reciprocal rank 0.758 to 0.817) and the sweep runs in about half the time; an expense-categorization run drops from about 157 ms to about 108 ms per query, and a 10 000-row invoice match from about 840 ms to about 320 ms per probe. Because the pool now holds different relations, individual rankings and the factor weights reported by$whycan shift slightly in either direction. See improving inference.
Fixed
- A
$not,$andor$oron a linked path now scopes the candidates and the evidence, not only the rows.{"product.id": {"$not": 0}}restricted which rows were counted but left the candidate pool and the evidence drawn from the whole linked table โ a200with plausible content and the wrong candidates. Every combinator over a linked path is now applied to both. - A
gethonours a where on the get field itself. An autocomplete query such asget: "name"with{"name": {"$startsWith": "mil"}}returned candidates from the column's whole distinct set, so unrelated values were offered beside the matching ones. The candidate universe now respects a same-field where the way it respects every other where. - Set-, array- and text-valued cells read consistently on the remaining paths, and a bit-set difference refuses a width mismatch instead of adopting the operand's width โ the cause of a negation that could answer with zero rows.
- A field reached through a null link is null. Through a nullable link
with no target on that row,
$match,$hasand$existscounted the row as present โ it read the linked table's first row โ so{"partN.name": {"$exists": true}}answered every row. Such a row now matches no operator, is excluded by$not, projects asnullinselect, and is selected by{"partN.name": {"$exists": false}}: the same rules as a nullable column read directly. - A modified link target is visible through the link at once. After
_modifychanged a row in a linked table, a path through that link could still read the row's previous values; the link now always resolves to the live row. - Excluding an item from a recommendation no longer offers it. A statement
about the item being inferred (
{"product": {"$not": 5}}while recommendingproduct) is a filter on the answer, never evidence for it; an excluded item โ including one the model had not seen โ is absent from the candidates instead of returned at rank 1. Statements about contextual data remain inference evidence, as before.
v2.8.3
The v2 API is in beta. A correctness-and-latency release: two silent
wrong-answer fixes the demo apps reported, a bit-operation bug that could
return plausible-but-wrong predictions, a broad prediction-latency pass, and a
deliberate narrowing of what basedOn is allowed to reach.
Changed
basedOnnow means exactly the fields it names. A field is paired when a known names it โ the engine no longer infers extra Text fields of a linked table whenever your query happened to carry any Text known. That inferred reach helped some shapes and hurt others, but the reason for removing it is that abasedOnis a statement of which evidence is permitted, and silently reaching past it made that statement advisory. If a prediction relied on the implicit reach, name those fields explicitly. Text evidence is now weighted by a fitted calibration rather than a fixed assumption. See improving inference.
Improved
- Prediction answers faster, across the board. A joined representation now
answers a position from the side that owns it (memoised per position set), a
linked text column seeks its token postings instead of re-expanding them,
numeric value priors are computed when read rather than once per candidate,
linked
selects resolve lazily, and the rotating cache evicts in constant time instead of scanning every slot on insert. The gains are largest on link-target predictions over wide linked tables.
Fixed
$noton a legacytablereturned nothing at all. On atype: "table"table queried through v2,{"field": {"$not": value}}returned zero rows instead of the complement โ a200with an empty body, indistinguishable from "no match". That is the basket-exclusion shape ($andof$not), so a legacy-backed database could serve no recommendations rather than reporting an error. Two independent causes were found and fixed: a numeric lookup that missed even when the value was present, and the negation itself._recommendignored a filter on a linked field. A$matchon a linked column constrained_searchcorrectly but not_recommend's candidate pool, which was drawn from the whole linked table โ200 OK, plausible content, wrong candidates. Every linked where-operator is now applied to the pool.- A bit-operation fast path could return a plausible wrong answer. When two
inputs were segmented differently, the dense fast path in
orcombined the wrong chunks: a shorter input raised an index error, a longer one returned an incorrect result with no exception. It is reachable from the predict path, so some predictions could be quietly wrong. Inputs are now aligned before they are combined. - Set-, array- and text-valued columns are read consistently. Membership was re-derived independently in several places, each matching only the shapes its author had in mind โ a column reads back as an array, a sequence, a set or a Java collection depending on codec and storage engine. Seven silent degradations are fixed and the derivation now lives in one place.
- The v2 documentation generator emits the wording its own artifact carries, so the published reference and the generated source no longer drift apart.
v2.8.2
The v2 API is in beta. One breaking change to _match's hit shape (below),
the contract fixes behind three defects our demo apps hit while migrating, and
a large cut in link-target prediction latency.
Changed
_matchhits carry$valuealone. The v1 spellingsfeatureandfieldare no longer returned by default:featurewas a verbatim copy of$value, andfieldwas always your own request'smatchparameter, so neither added anything. Every ranked-value op โ_predict,_recommend,_matchโ now reads identically, which is what the$valuerename was for. Porting a v1 body that reads them?select: ["feature"]andselect: ["field"]still return them, unchanged. See the v2 introduction.
New
- Rank candidates by an aggregate.
select: ["$f"]and{ "$sum": { "$context": โฆ } }no longer require the query to also carry a matchingorderBy, andorderBy: { "$sum": { "$context": โฆ } }is now accepted. So "which candidate values contribute most to the goal" is one query, instead of pulling the whole candidate set and sorting it client-side. See the query reference.
Improved
- Link-target predictions answer several times faster on a fresh request.
Column-only representations read rows directly, a query runs one prediction
instead of one per candidate column, and the
$whyexplanation is built only for the rows that ask for it. - A write costs less afterwards. Class counts and linked-attribute buckets are now kept per segment, so appending data re-counts only the segment that changed rather than the whole collection โ the first query after a write is much closer to the warm one.
- Steadier memory on long-lived instances. Caches keyed by data lineage are bounded by the generations still retained rather than by an entry count, so they follow what the instance is actually holding.
- An attribute counts once per candidate. When several pieces of evidence pointed at the same attribute of a candidate, it could be credited more than once and inflate that candidate's probability. It is now counted once.
Fixed
- Per-candidate aggregates across a link returned zero.
$fand{ "$sum" | "$mean": { "$context": โฆ } }over agetwhose path traverses a link โproduct.name,context.weekโ reported0for every candidate, with a200and a well-formed body. Direct fields and the link field itself were correct, so a chart could show a confident flat line at zero for a product with hundreds of purchases. They now resolve the candidate's rows through the field that owns them. relate: { "$props": โฆ }accepts set-valued properties. A property held in aSetcolumn (product.tags) was refused with a message saying no rows carried the value โ while rows plainly did, because membership and equality are different questions about the same data. Set-valued properties, and a list of values for one property, both work now.- An empty
$andor$orclause list. Building a filter list that turns out empty โ{ "id": { "$and": [] } }from an empty basket โ failed the request. An empty$andnow constrains nothing and an empty$orselects nothing, which is what each means and what v1 already did. - A union view's refusal names the branch again. When an output column resolves in no branch, the error names each branch and the columns it tried, rather than only reporting that every projection was absent.
- The v1 type reference no longer prints internal class names. A few
anonymous formats reached the served pages as a Java class name and an object
hash instead of their type, in the
AnyValueandRegressionExplanationreferences.
v2.8.1
The v2 API is in beta. A follow-up to v2.8.0: query latency on large
collections, better link-target predictions, and a set of correctness fixes
around evidence and aggregation โ plus one new relate form.
New
- Relate an entity's own property values.
relate: { "$props": โฆ }asks what distinguishes a row's own attributes against thewhere, so "what is unusual about this customer" is a single query rather than one per field. See the query reference (and the SQL spelling in the SQL reference). config.aiapplies on v1_recommend,_matchand_search. Where it genuinely cannot apply โ_relateand_similarityโ the request is now refused with a 400 instead of being accepted and ignored, so a tuning setting never silently does nothing. See improving inference.
Improved
- Large collections answer much faster. Broad selects, page fetches and
orderByon a plain field no longer pay a per-query floor; ordering reads the index's own value order, and computed fields are evaluated over the filtered universe rather than the whole collection. The biggest gains are on a scopedorderByover a computed field, which was the slowest shape at scale. - Leaner prediction. Per-state ordinal maps, segment-lifetime column reuse and primitive cursors in the bit loops cut allocation churn on the predict path, so repeated inference on a warm collection holds a smaller footprint.
- Better link-target predictions. A link candidate is now scored through its target's attributes under a field-pair prior, so a candidate is judged by what it actually looks like rather than only by how often it has been seen.
- Name matching fades with history. The name-identity boost decays as a row accumulates history instead of being discounted by query coverage, so a well-established entity is ranked on its record rather than its name.
Fixed
- A
whereon the predict target restricts candidates, not evidence. Naming the target inwherenarrowed the candidate set and was counted as evidence for it, which skewed the probabilities; it now only restricts. - Deleted rows no longer form a group. A deleted row could still produce its
own
GROUP BYgroup;$fand$contextaggregates now intersect the live rows. whyanswers on a large collection. The KNN explanation is bounded, so_estimatewithwhyno longer fails as the collection grows, and it reads evidence rows from the collection rather than the field.basedOnon a text name no longer regresses link-target predict.- Env branches survive a binary-format upgrade. After v2.8.0's format bump an env branch failed plain reads with a raw internal error until it was repaired by hand. Branches now migrate on first use, and a read against a not-yet migrated state returns a clear error naming the remedy instead of an internal failure. See environments and common errors.
- Documentation rendering. Table captions rendered as literal markup on 19 pages, and a dark page backdrop made overflowing text unreadable.
v2.8.0
The v2 API is in beta. This release is mostly speed and memory at scale โ text postings and column indexes are now prepared at write time and read straight from memory-mapped storage instead of being decoded onto the heap โ alongside a wider SQL surface and explanations that point at the evidence inside a predicted item.
New
- Derive a table from a query.
INSERT โฆ SELECTandCREATE TABLE โฆ AS SELECTbuild a table in the database from a query over existing ones, so a derived dataset no longer round-trips through your client. See the SQL guide. evaluate()andestimate()as SQL table functions. Measure accuracy and get a prediction's estimate from SQL, withtest/testLimit/train/resubstitutionarguments to control the split. See the SQL reference.- SQL answers in PostgreSQL result format.
/api/v2/_sqlcan answer with rows and column metadata in the pg result shape instead of the hits shape, so a Postgres client renders it directly. See the SQL reference. - Multi-column
GROUP BYand multi-keyORDER BY. Group by joint tuples and order by several keys, with Postgres's default null placement per key. See the SQL reference. havingโ filter candidates on the server. Filter ranked results on$fafter ranking and beforeoffset/limit, so an "at least N occurrences" cut happens in the engine instead of in your client. See the query reference.- Explain a predicted link or text target through its own attributes. When
the prediction is a linked row,
$whynow shows the candidate's own fields as the prior that carried it, rather than leaving that path unexplained. See the inference guide. $highlighton a predicted target. Add it to a link-target predict'sselectand each candidate comes back with its own field values marked where the evidence sits โ ready to display. See the query reference.- Structured JSON logs.
AITO_LOG_FORMAT=jsonwrites one JSON object per line to stdout, with context such asdatabaseinlined as a field, so a self-hosted instance ships into your existing log pipeline with no parsing rules. See running it on your own infrastructure. - Union views are served live. A stored union view now answers from its
sources once one of them changes, so it cannot go stale;
_refreshbecame an optimisation rather than a correctness step. See schema design.
Improved
- Faster and much lighter at scale. Columns and token postings are persisted in read-ready form, so every column type opens in constant time and reads stay memory-mapped rather than decoding whole blobs onto the heap. Large states use substantially less query-time memory and answer faster.
- Better link predictions. Predicting a linked field now draws candidates from the linked table rather than only the values seen in training, so a valid target that never appeared in the training rows can still be predicted and ranked. See the inference guide.
- The first query after a write. A
recommendissued right after a write no longer pays to refit the prior before answering. - Ranking on catalogue-shaped data. Name-based identity matching is now bounded by the evidence that supports it instead of a fixed internal ceiling, which improves ranking where many rows share similar names.
- Quieter error logs. Client-input errors are no longer logged at ERROR with stack traces, so an operator's error channel reflects server problems. See common errors.
Fixed
- One
evaluatetrain/test contract across engines. An undefinedtrainnow means the complement of the test set on both engines, andtestSourcealigns with it โ so the same request reports the same accuracy whichever engine backs the table. See evaluation. - Baselines measure the right population. An evaluation's baseline is the model with no evidence, scoped by the query's own filters rather than a wider pool; filtered evaluations previously reported an optimistic baseline.
estimateover a nullable numeric column estimates over the non-null subset instead of failing.- Explanations on temporal columns. A KNN
$whyover a v2 collection's temporal columns no longer errors. - v1 schema
GETrenders every scalar type reachable from v2 instead of failing with a 500. relateandsimilarity.relatereported inverted frequencies, andsimilaritydiscarded partial evidence; both fixed.- Deleted and superseded rows. A deleted row now releases its primary key, and a superseded row is no longer offered twice as a prediction candidate.
- Merged
Array/Setcolumns. A width mismatch between the merge writer and reader could decode stored values into wrong ones; fixed. - SQL that real tools emit. Comments, positional
INSERT, and aFROM-lessSELECTnow parse. See the SQL reference. - pgwire. A duplicate column name keeps Postgres's own answer instead of
being renamed, and a data error reports
22P02/22P04rather than a syntax error. See the SQL reference.
v2.7.0
The v2 API is in beta. This release unifies the v2 response contract across
storage engines, extends where filtering (Date ranges and let fields), and
adds on-prem observability with a Prometheus metrics endpoint.
New
- Prometheus metrics endpoint.
GET /metricsexposes request counts, a request-latency histogram, and JVM/disk gauges in Prometheus text format โ per database โ so a self-hosted Aito scrapes straight into Prometheus and Grafana. See monitoring your instance. - Filter on
Dateranges.wherenow accepts range comparisons onDatecolumns, so "orders in the last quarter" is expressible directly. See the query reference. - Filter on
letfields. A field defined withletcan now be used inwhereโ equality,$in, and same-field$ormembership โ so a value you derive filters the same query that defines it. See the query reference.
Improved
- One response contract across engines. A v2 endpoint now returns the same
response shape regardless of which storage engine backs the table โ one
selectvocabulary, one similarity-score key, a consistent errorkind, and a response-time header โ so client code no longer special-cases the engine. - Honest HTTP error codes. Capability and input errors are classified into
proper 4xx codes with a machine-readable
kind, instead of returning prose in the error field. See common errors. - Faster v2 queries. Leaner bitset intersections, single-pass posting-list walks, and segment-aligned per-known tails cut work on large states; the AND-prior evidence gate is now on by default.
- On-prem operations. An operations runbook and a
restore-statecommand with a backup/restore drill for self-hosted deployments, and production no longer logs at DEBUG by default. See clusters & operations.
Fixed
- Namespaces and mounted sub-envs no longer 500. SQL DDL/DML and v2 schema/data/query addressed at a namespace or a mounted sub-env returned a 500 (an internal cast error); they are now handled correctly.
_evaluatehonoursbasedOn._evaluatesilently ignoredbasedOn, which voided any measurement run through it against a based-on database; fixed.- Filters no longer dropped under a tenant scope.
recommendandrelateover a v1-backed table could silently dropwherefilters when run under a tenant scope; fixed. - Nullable columns. A nullable column could leak one row's value onto rows that have none; fixed.
- Empty-tokenising text filters. A text
wherewhose value tokenised to nothing matched the whole table instead of nothing; fixed. - SQL over pgwire. Column names are now identical across both transports, and notices are delivered.
v2.6.1
The v2 API is in beta. This release is mostly stabilization โ SQL/pgwire correctness and lower memory at scale โ with a few additions to the SQL surface.
New
- Prediction as a SQL table function.
SELECT * FROM predict('invoices', 'category', given => 'vendor = ''Acme''')(andpredictions(โฆ, k => 5)) runs a hypothetical prediction for a row you don't have yet โ the SQL spelling of what_predictdoes over JSON. See the SQL reference. - List/Set member edits in
_modify.update'ssetaccepts{"tags": {"$add": ["sale"], "$remove": ["draft"]}}to add or remove members of a list/Setcolumn per matching row โ additive and safe under concurrent updates. - SQL table aliases.
FROM customers AS candFROM customers cnow parse, so BI tools, ORMs and query builders that alias tables (and both sides of a JOIN) work as written.
Improved
- Faithful transactions over pgwire. A multi-statement request is now one
implicit transaction on the extended protocol too (pgjdbc and most drivers), so a
failed statement no longer leaves earlier ones committed.
DISCARD ALLnow fully resets connection state โ important for connection poolers. - Honest error codes. Capability gaps return
0A000(feature not supported) instead of42601(syntax error), so federating clients (Metabase, DuckDB, postgres_fdw, SQLAlchemy) fall back gracefully instead of reporting your query as broken. Clients can also introspect available functions via a generatedpg_proc. - A prediction column is bounded by default.
SELECT predictions(col) FROM tnow defaults toLIMIT 10instead of running one inference per row over the whole table โ matchingrecommend/relate/searchand the JSON API. - Lower v2 memory at scale. Several unbounded rep2 caches are now bounded and a per-query mask is leaner, continuing v2.6.0's 10M-scale reductions.
Fixed
- Correct results in large multi-segment states. Fixed a case where a state with more than 64 segments could silently drop recommendation/prediction evidence.
- Three SQL engine bugs found by a new conformance suite, including
WHERE price < 5 OR price > 50no longer erroring.
v2.6.0
The v2 API is in beta. This release adds new query surface and continues hardening it.
New
- SQL is a full query surface now. Rank candidate values with
match(โฆ), mine frequent patterns withpatterns(โฆ), run a relevance search withaito.search(โฆ)(carrying$why,$highlightand$matches), and find nearest vectors withORDER BY col <-> '[โฆ]' LIMIT kor theaito.knn/aito.nnconditions. See the SQL reference. - Vector predicates for prediction โ
aito.clusterandaito.semanticasWHEREconditions, andvector(n)columns to hold embeddings. - TIMESTAMP columns over SQL. Declare and range-filter timestamps, do
now()+ interval arithmetic in aWHERE, read components (EXTRACT,date_part, day-of-week in both spellings), and bucket withdate_trunc. See the SQL reference. - Define your schema in SQL โ
CREATE TABLEdeclaring links and analysed text, andCREATE VIEWover the query functions. - Real SQL transactions โ
BEGIN/COMMIT/ROLLBACKare buffered and merged on commit, with working savepoints for partial rollback. - Aito's functions live in an
aitoschema, keeping the public namespace clean while the key concepts stay reachable unprefixed. select: [field.$predictions]returns a field's ranked predictions inline in a v2 query.
Beta stabilization
Hardening across the v2 engine and its SQL surface:
- More accurate predictions by default. Name-based identity boosting is now off by default โ on a 10M-row benchmark it was costing ~30 points of top-1 accuracy by letting a name match take over the score. Predictions that relied on the old default will change, and should improve; the boost is still available and is now bounded, so it can add signal without dominating. See improving inference.
- Much lower v2 memory at scale. Large v2 predictions allocate far less per call, and the internal firing cache now has a real size budget with eviction โ a 10M-row workload no longer balloons memory.
- No more 15-minute hangs on out-of-memory. A fatal out-of-memory error mid-request used to stall the connection until the request timeout and then return an unusable body; it now fails fast and cleanly.
- Assorted correctness fixes. A
Textcolumn added to an already-populated table now feeds a$textview; and over the SQL wire, a data query that mentions a catalog name is answered from your data (not the catalog), a partial rollback no longer discards the whole transaction,CREATE VIEWvalidates its functions up front instead of degrading silently,COLLATEis honoured, andGET /versionreports the real version.
v2.5.3
New
- SQL over the Postgres wire protocol is on by default, and the Docker image
now ships with API keys configured. Connect
psql, JDBC/ODBC or psycopg straight at Aito โ see the SQL guide. - TLS is required for the SQL listener on a shared port, and SQL sessions are bounded per connection.
Improved
- A read-only API key can no longer open a read-write SQL session โ the SQL surface now honours key scope the same way the REST API does.
- Each database is authorized by its own keys, not the server's.
Fixed
$whyexplanations keep their highlights when scoring composes several factors โ highlight output no longer nests one level too deep.- A refused SQL listener no longer takes the server down with it; a SQL session no longer outlives the database it was opened against.
v2.5.2
Fixed
$whylifts and highlights are reported at the level clients expect, so$highlight/$matchesresults render correctly again.
v2.5.1
New
- Hybrid search: combine BM25 text relevance with vector similarity in one
ranked query โ
$vectorSimilarityand the self-calibrating$vectorIdfblend with$p. See vector search. /api/v1runs on the v2 engine for the query family, so v1 clients get v2 performance without changing a line.- The vector dimension cap is lifted (previously ~1017 dimensions).
Improved
selectaccepts$highlight/$matchesas aliases for$whyhighlights.
Fixed
- Cross-tenant isolation:
$and/$notover a linked field no longer returns rows from another tenant's data. _matchon a text column now predicts per token, matching v1 behaviour._estimaterestores v1 per-field attribution inwhy, and defaults to AdjustedKNN as v1 does.- Several v1โv2 response-shape differences corrected (
_predict,_recommend,_relate,_match), so a v1 client sees the shape it expects.