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 SQL BETWEEN, or any two comparisons on the same column โ€” failed with an internal bit & operation requires same sized bit sets instead of answering. One-sided comparisons were always correct, and the v2 engine was never affected. See the query reference.
  • relate and $patterns on 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 โ€” .$distinct and .$token. A Text column carries both its distinct whole values and its analyzed tokens; relate, $patterns and $props can now name either (product.name.$distinct, product.name.$token). $patterns offers 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 $gte on numbers, strings and timestamps, plus $startsWith, $mod and 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+text beating 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 whose name is exactly BASF 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, _recommend or _match, or when ordering by $p, a bare Text value still counts word by word. See the query reference.

New

  • relate a 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.
  • _evaluate measures per-member predictions and combined test selectors. predict: "tags.$feature" evaluates a multi-label prediction member by member, and a test selector can combine $index with 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. _recommend now 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 with basedOn. A For You shelf ranked from the shopper's profile went from 0.11 to 0.18, and 0.24 with basedOn. 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.
  • basedOn applies to _recommend. v2.8.4 accepted it and ignored it.
  • relate on 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 200 with 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, $startsWith and $search apply 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, $or or $in against 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 basedOn arms 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 $why can shift slightly in either direction. See improving inference.

Fixed

  • A $not, $and or $or on 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 โ€” a 200 with plausible content and the wrong candidates. Every combinator over a linked path is now applied to both.
  • A get honours a where on the get field itself. An autocomplete query such as get: "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, $has and $exists counted 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 as null in select, 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 _modify changed 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 recommending product) 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

  • basedOn now 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 a basedOn is 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

  • $not on a legacy table returned nothing at all. On a type: "table" table queried through v2, {"field": {"$not": value}} returned zero rows instead of the complement โ€” a 200 with an empty body, indistinguishable from "no match". That is the basket-exclusion shape ($and of $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.
  • _recommend ignored a filter on a linked field. A $match on a linked column constrained _search correctly 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 or combined 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

  • _match hits carry $value alone. The v1 spellings feature and field are no longer returned by default: feature was a verbatim copy of $value, and field was always your own request's match parameter, so neither added anything. Every ranked-value op โ€” _predict, _recommend, _match โ€” now reads identically, which is what the $value rename was for. Porting a v1 body that reads them? select: ["feature"] and select: ["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 matching orderBy, and orderBy: { "$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 $why explanation 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. $f and { "$sum" | "$mean": { "$context": โ€ฆ } } over a get whose path traverses a link โ€” product.name, context.week โ€” reported 0 for every candidate, with a 200 and 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 a Set column (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 $and or $or clause list. Building a filter list that turns out empty โ€” { "id": { "$and": [] } } from an empty basket โ€” failed the request. An empty $and now constrains nothing and an empty $or selects 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 AnyValue and RegressionExplanation references.

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 the where, 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.ai applies on v1 _recommend, _match and _search. Where it genuinely cannot apply โ€” _relate and _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 orderBy on 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 scoped orderBy over 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 where on the predict target restricts candidates, not evidence. Naming the target in where narrowed 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 BY group; $f and $context aggregates now intersect the live rows.
  • why answers on a large collection. The KNN explanation is bounded, so _estimate with why no longer fails as the collection grows, and it reads evidence rows from the collection rather than the field.
  • basedOn on 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 โ€ฆ SELECT and CREATE TABLE โ€ฆ AS SELECT build 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() and estimate() as SQL table functions. Measure accuracy and get a prediction's estimate from SQL, with test / testLimit / train / resubstitution arguments to control the split. See the SQL reference.
  • SQL answers in PostgreSQL result format. /api/v2/_sql can 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 BY and multi-key ORDER 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 $f after ranking and before offset/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, $why now shows the candidate's own fields as the prior that carried it, rather than leaving that path unexplained. See the inference guide.
  • $highlight on a predicted target. Add it to a link-target predict's select and 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=json writes one JSON object per line to stdout, with context such as database inlined 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; _refresh became 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 recommend issued 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 evaluate train/test contract across engines. An undefined train now means the complement of the test set on both engines, and testSource aligns 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.
  • estimate over a nullable numeric column estimates over the non-null subset instead of failing.
  • Explanations on temporal columns. A KNN $why over a v2 collection's temporal columns no longer errors.
  • v1 schema GET renders every scalar type reachable from v2 instead of failing with a 500.
  • relate and similarity. relate reported inverted frequencies, and similarity discarded 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/Set columns. 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 a FROM-less SELECT now 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 / 22P04 rather 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 /metrics exposes 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 Date ranges. where now accepts range comparisons on Date columns, so "orders in the last quarter" is expressible directly. See the query reference.
  • Filter on let fields. A field defined with let can now be used in where โ€” equality, $in, and same-field $or membership โ€” 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 select vocabulary, one similarity-score key, a consistent error kind, 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-state command 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.
  • _evaluate honours basedOn. _evaluate silently ignored basedOn, which voided any measurement run through it against a based-on database; fixed.
  • Filters no longer dropped under a tenant scope. recommend and relate over a v1-backed table could silently drop where filters 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 where whose 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''') (and predictions(โ€ฆ, k => 5)) runs a hypothetical prediction for a row you don't have yet โ€” the SQL spelling of what _predict does over JSON. See the SQL reference.
  • List/Set member edits in _modify. update's set accepts {"tags": {"$add": ["sale"], "$remove": ["draft"]}} to add or remove members of a list/Set column per matching row โ€” additive and safe under concurrent updates.
  • SQL table aliases. FROM customers AS c and FROM customers c now 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 ALL now fully resets connection state โ€” important for connection poolers.
  • Honest error codes. Capability gaps return 0A000 (feature not supported) instead of 42601 (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 generated pg_proc.
  • A prediction column is bounded by default. SELECT predictions(col) FROM t now defaults to LIMIT 10 instead of running one inference per row over the whole table โ€” matching recommend/relate/search and 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 > 50 no 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 with patterns(โ€ฆ), run a relevance search with aito.search(โ€ฆ) (carrying $why, $highlight and $matches), and find nearest vectors with ORDER BY col <-> '[โ€ฆ]' LIMIT k or the aito.knn / aito.nn conditions. See the SQL reference.
  • Vector predicates for prediction โ€” aito.cluster and aito.semantic as WHERE conditions, and vector(n) columns to hold embeddings.
  • TIMESTAMP columns over SQL. Declare and range-filter timestamps, do now() + interval arithmetic in a WHERE, read components (EXTRACT, date_part, day-of-week in both spellings), and bucket with date_trunc. See the SQL reference.
  • Define your schema in SQL โ€” CREATE TABLE declaring links and analysed text, and CREATE VIEW over the query functions.
  • Real SQL transactions โ€” BEGIN / COMMIT / ROLLBACK are buffered and merged on commit, with working savepoints for partial rollback.
  • Aito's functions live in an aito schema, 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 Text column added to an already-populated table now feeds a $text view; 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 VIEW validates its functions up front instead of degrading silently, COLLATE is honoured, and GET /version reports 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

  • $why explanations 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

  • $why lifts and highlights are reported at the level clients expect, so $highlight / $matches results render correctly again.

v2.5.1

New

  • Hybrid search: combine BM25 text relevance with vector similarity in one ranked query โ€” $vectorSimilarity and the self-calibrating $vectorIdf blend with $p. See vector search.
  • /api/v1 runs 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

  • select accepts $highlight / $matches as aliases for $why highlights.

Fixed

  • Cross-tenant isolation: $and / $not over a linked field no longer returns rows from another tenant's data.
  • _match on a text column now predicts per token, matching v1 behaviour.
  • _estimate restores v1 per-field attribution in why, 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.