Skip to main content
This page says what Brussle is, and the four problems that it solves for AI answers about your records. Read it before the other pages.

A database for AI answers

Brussle is a database. In Brussle, a column can be the answer that an AI model gives about a document. We call this column a judgment. We call the AI model that makes its answers the engine. An answer can read other documents. Brussle keeps each answer current when those documents change. Each answer records the question version and the engine version that made it. You can measure each answer against what actually happened. You decide what to ask, what an answer means and what to do with it. Brussle keeps the answers correct as data: current, versioned, traceable and measurable.

Four problems that an AI column cannot solve

You can already keep an AI answer in a column of your own database. A trigger runs when a record changes. A worker calls an AI model and writes the answer back. If the answer reads only its own record, this column is enough. The column fails on four problems. Brussle exists to solve them.

1. The answer depends on other documents

Some questions about a record need other records. Which open invoice does this payment settle? needs the open invoices. Did this contract section change in a way that matters? needs the section as it was at the last check. A trigger on one table does not know which records in other tables read the changed record. To answer such a question in your own database, you must build and run these parts:
  • Code that fetches the related records for each question and cuts them to fit the AI model’s context.
  • Code that finds each record that a write affects. One write can affect many records.
  • A queue and workers that call the AI model again for each affected record.
  • A debounce, so that a burst of 100 writes does not cause 100 calls for one record.
  • A limit on cost. A change to one product can affect thousands of order lines.
  • A way to know which version of each related record an answer read.
Each new relation makes this code larger. In Brussle, a relation makes a judgment read other documents. Brussle does each of these parts for you:
  • The judgment’s context recipe says which related documents to read, how many, and which of their fields. Brussle fetches them and cuts the context to fit.
  • Brussle finds the affected documents through the attribute that you already write.
  • You can set a debounce, so that Brussle waits until changes settle. A ceiling makes sure that Brussle still judges a busy document.
  • Some documents are read by many others, such as a product that thousands of order lines point at. For a change to such a document, a re-judge scope limits which documents Brussle judges again. A rolling limit caps how many documents Brussle judges again in this way in 30 days.
  • Before Brussle creates a judgment that reads related documents, it shows an estimate of the monthly cost. Brussle creates the judgment only when you confirm.
  • Each answer records the position in the namespace’s log at which Brussle read the related documents.
A relation reads the documents that your ids connect, not similar text. See relations and, for the cost controls, freshness policies.

2. The answer must say whether it is current

A column holds the last value that a worker wrote. The column cannot tell you whether that value still agrees with the records that it read. In Brussle, each answer has a freshness. An answer is fresh when it agrees with each document that it read, at their current revisions, and with the judgment’s active version. If not, the answer says so, for example with pending or stale. A stale answer gives its cause in stale_reason. A query can send answers: "fresh_only" to leave out each document whose answer is not fresh. See freshness.

3. A change to the question or the engine must not mix old and new answers

When you change a prompt or an AI model, old answers and new answers mix in the column. Nothing records which answer came from which. In Brussle, a judgment’s definition has versions, like a schema:
  • Each answer records its judgment_version and its engine_version. Queries can filter on both.
  • After you activate a new version, an answer from an older version that asks differently reads stale, with the reason older_version. Thus, answers: "fresh_only" leaves it out.
  • Activation judges nothing again. A backfill judges those documents again, and shows its cost before you confirm it.
  • The engine can change its behaviour. Each period of the same behaviour is an epoch. The engine_version of each answer names its epoch. A new epoch alone does not make an answer stale.
See versions.

4. The number must be measurable against what happened

The engine says 0.8, and a column stores 0.8. Nothing tells you how often 0.8 comes true on your data. In Brussle, you record outcomes. An outcome is what actually happened to a judged document. You post outcomes, or outcome rules make them from writes that you already send. Brussle keeps your outcomes beside the answers. The calibration report measures the answers against them. From 100 outcomes, with at least 20 for each of two different values, Brussle fits a correction of the probabilities on your outcomes. Brussle uses the fit only when it does better than the raw numbers on outcomes that the fit did not use. See calibration.

What you can rely on

You can rely on four things about each answer:
  • The answer is the engine’s answer to exactly the inputs that its evaluation (the permanent record of one judging) records.
  • The answer is current with respect to those inputs, or it says that it is not.
  • The answer records the question version and the engine version that made it.
  • You can measure the answer’s number against the outcomes that you record.
Brussle does not promise that an answer is correct, or that a judgment answers your real question. You measure that with your outcomes. See what you can rely on.

When an answer reads one document

If an answer reads only its own document, use a column in your own database. A trigger and a direct call to an AI model are enough. Each answer then costs less than in Brussle. See Brussle versus an AI column in your own database.

An example: a payment and its open invoices

You copy your records to Brussle with one write API. Brussle keeps each copy as a document. Payments and invoices are documents in one namespace (the container for one tenant’s documents):
  • A payment has attributes.kind: payment and attributes.status: unmatched.
  • An open invoice has attributes.kind: invoice and attributes.status: open.
  • Each payment and each invoice has a key in attributes.block, for example the counterparty and the currency.
You define a choice judgment, settles, on unmatched payments: Which of these open invoices does this payment settle? Its relation reads the open invoices in the payment’s block. Each invoice becomes one option. The answer is none_of_the_above when no invoice fits. Then this happens:
  1. A payment arrives. Brussle judges it with the open invoices of its block. The answer names one invoice or none_of_the_above, with a probability.
  2. A new invoice arrives in the same block. The payment’s answer becomes pending. Brussle judges the payment again.
  3. Your team settles a payment. You write the match and the new status of the payment in one write. An outcome rule records the match as an outcome.
  4. The calibration report measures the answers of settles against these outcomes.
You did not write code to find the payments that a new invoice affects, to judge them again, or to record which invoices each answer read. Whether the matches are good on your data is yours to measure. The quickstart does this example step by step. Match one record to another has the full definition.

What a relation can read

A relation lets a judgment read more than the document that it judges. Each kind of relation fits a different kind of question: You do not need to know your relations before you start. Find your relations shows which of your attributes already point at other documents.

How it fits together

  • A namespace holds documents. Usually you have one namespace for each tenant (one of your own customers) in each environment, for example production or staging. A relation reads only documents in the same namespace.
  • A document is your copy of one record in Brussle. It has attributes, which queries can filter on, and a JSON state, which judgments read. An attribute can hold the id of a different document. This attribute is how documents point at each other.
  • A judgment is a column that the engine fills in: a typed question (bool, choice or score) on a namespace. The judgment names one engine version. That engine version makes its answers. The definition of a judgment has versions.
  • A relation is part of a judgment. It tells the judgment which other documents to read, and what to read from them. See relations.
  • An answer is the current result of one judgment for one document. It is a probability distribution, not free text. It also shows its freshness and its provenance, that is, the versions that made it. Queries can filter and sort on answers.
  • An evaluation is the permanent record of one time that Brussle judged a document. It holds the exact text that the engine read, including the related documents, and a reference to each image that it read. Brussle keeps each evaluation.
  • An outcome is what actually happened to a judged document. You post outcomes, or outcome rules make them from your writes. Brussle measures answers against your outcomes. It also calibrates the probabilities on them.
Start with the quickstart. Before you build on Brussle, read what you can rely on and the tradeoffs.

Where it fits

Brussle works next to the database that you already have. Your database stays the system of record. Brussle holds a copy of the records that your questions need. Brussle judges in the background. An answer usually arrives a few seconds after the write that needs it. A burst of writes waits its turn. To read an answer that already exists takes a fraction of a second. Thus, Brussle fits work that runs beside your product: matching and reconciliation, review queues, triage queues, moderation, and fraud and abuse review. Brussle is not made to sit inside a user’s request. If a page must wait for a new AI answer before it can respond, call an AI model directly. A request can read an answer that already exists. Only the wait for a new answer is slow. See tradeoffs.

The one thing to learn: what the engine sees

Each judgment has a context recipe. The recipe lists what the engine reads to make an answer: fields of the document, and relations. The engine answers only from what the recipe shows. The recipe controls two things:
  • The cost of an answer.
  • When Brussle judges a document again. A change to something that the recipe does not read does not cause a new judgment. This rule applies to related documents too.
Use this pattern for each judgment:
  1. Ask a narrow question about one document.
  2. Put the right related document beside it in the recipe, for example the open invoices of a payment, or a section as the judgment last saw it.
  3. On a bool judgment, keep numbers, such as counts, as features, with "render": false in the relation’s aggregate. The fit on your outcomes weighs them.
The engine reads text, and images if it reads images. It does not forecast. For a question about what will happen, the counts and the fit carry the number. The calibration report’s features_only shows what the engine adds to your own numbers. Start from a starter judgment, and change it as necessary. A starter judgment has a recipe for a common question. Several starters already read related documents, for example queue context, linked accounts and hierarchy.

What else you get

  • A recipe that costs no more than it must. When a judgment has enough outcomes, Brussle tests smaller versions of its recipe on your labelled outcomes. Brussle suggests a smaller recipe only when it costs less and the accuracy on your outcomes does not drop. See let your outcomes tune the recipe.
  • Answers that you can explain later. Brussle keeps each evaluation. An evaluation holds the exact context that the engine saw, the answer and the versions that made the answer.
  • Predictable cost. Brussle does not judge a document again if its relevant content did not change. A backfill (the first judging of the documents that already exist) shows its cost before you confirm it. Budgets limit what each namespace can spend.
  • Many tenants. With template judgments, you define a question one time for the namespaces of all your tenants. You can also set budgets per tenant.

What you pay for

You pay for judging per judgment, by size class, times the weight of the engine. If the context and the question of an answer fit in 2,000 tokens, the answer is one standard judgment. A larger answer counts as more than one judgment. The context includes the related documents that the recipe reads, and the images that the engine reads. On an engine with a weight above 1, each answer counts more: on gpt-6-luna, a standard answer counts as 2.5 judgments. Brussle judges a document again only in two cases:
  • What its judgment reads changes. This includes a change to a related document that the judgment reads.
  • The interval of a periodic judgment ends. A periodic judgment judges each document again, whether or not the document changed.
Each answer costs more than a direct call to an AI model. To see what your own traffic costs, price it on the pricing page.