Skip to main content
The numbers you already keep can go into the correction beside the engine’s answer: a plan’s seat count, an invoice amount, how long ago a contract was signed, how many invoices are overdue, what the engine said last time. The fit weighs them against your outcomes, so numbers the engine does not weigh can move the probability, and can reorder your documents. The engine never reads a feature, so a feature costs no engine call. A judgment with features is applied, like any correction, only when it beats the engine’s own numbers on your held-out outcomes. A bool, a choice and a score can all have features, and a judgment does not need relations to have them.

Add features

Name them in features. This judgment reads an account’s notes, and keeps the account’s seats, the days since it signed, and the engine’s previous answer:
  • state.seats is the number on the account itself.
  • age(attributes.signed_at) is the days since signed_at, measured from the account’s own updated_at at the revision Brussle judged.
  • previous.p is the engine’s own p the last time it judged the account. It is missing for the first answer.
Each answer shows its values in features, and so does its evaluation. A write that changes only a feature, such as state.seats, re-records the answer with no engine call and no charge. With an age feature, so does a write of the same content, because the age moved. From 100 outcomes with 20 of each kind, the fit can weigh the features with the engine’s answer. A feature change is a version change. Adding, removing or renaming a feature makes a new version, with its own fit. The earlier version’s answers read stale once you activate the new one. A new version starts with the engine’s p until its own outcomes are enough.

What a feature can read

A judgment keeps up to 12 features, from any of these sources: Ages. An age is a number of days, with a fraction: 36 hours is 1.5.
  • Each age is measured from the judged document’s own updated_at, at the revision Brussle judged, never from a related document’s write or the clock. A write to a related document never moves an age. A new revision of the judged document does, even with the same content.
  • The time must be an RFC 3339 string, such as "2026-10-01T09:30:00Z". A number, such as seconds since 1970, or other text is missing.
  • A time later than the judged document’s updated_at gives a negative age.
  • age(updated_at) is refused: at the judged revision it is always 0. Name the time you mean, such as age(attributes.approved_at).
previous.p is the engine’s p from the most recent time it judged the document, with the same engine version, under a version of the judgment that asks the same way.
  • It is missing for the first answer, after the document is deleted and created again, after the engine version changes, and on a composite, which has no single p.
  • It is the engine’s p, never the corrected one.
  • When only a feature moves, it stays the same (see reuses).
  • Outcomes replayed after the engine changes read it as missing, because their earlier answers came from the other engine.
What Brussle refuses. A feature on a path that an outcome rule reads, where one path is the same as the other or inside it, is invalid_request: for example state.refund.amount and a rule on state.refund. Such a feature would read the outcome itself. Brussle checks this when you create a version and when you set rules, against every version of the judgment. A rule’s path on a related document is not checked.

Ask about what will happen

The engine answers well about what a document shows now, and near chance about what will happen. For a question about what will happen:
  1. Ask a narrow question about what the document shows now.
  2. Keep counts from related documents as features, with "render": false so the engine does not read them.
  3. Set a horizon, and record what happened as outcomes.
  4. Let the fit on your outcomes weigh the engine’s answer and the counts.
This judgment asks what the customer’s messages say about an invoice, and keeps one count: the customer’s invoices paid late in the last year. The outcome records whether the invoice went overdue within 30 days.
Measure what the engine adds. In the report, each epoch’s held_out_metrics scores features (the features and the engine’s answer together) and features_only (the features alone) on the same held-out outcomes. If features_only scores as well as features, the engine adds nothing your counts do not already carry, and a feature threshold on the counts is enough.

How the fit with features works

  • One probability. The answer has no calibrated object. p is the selected correction, applied when the answer is read, and raw.p is the engine’s own answer, whose meaning never changes. fit says which correction p used: id, interval, run, pinned, fitted_at, outcomes, method and from_previous_epoch. method is features when the fit uses the features, and early or full when it corrects only the engine’s p. fit.interval is the 95% interval of p at this answer’s features. fit is null while p is the engine’s.
  • Chosen like any fit. Each run makes a fit that uses the features beside the fit on the engine’s answer alone, under the same minimums. The fit with features is chosen only when it beats both the engine’s p and the other fit on outcomes it did not use; the outcomes of one document are always held out together. If neither beats the engine, p stays the engine’s. Below 100 outcomes the fit is reported and never applied (readers_need_full).
  • All readers use p. Thresholds, filters, ranking, subscriptions and the judgments that read it use the corrected p (on a choice or score, the corrected numbers). A threshold on a feature still reads the stored feature. Groups cannot count a judgment with features. The evaluation log and the export keep the engine’s p and the features.
  • Automate on the bound. Queries and subscriptions can filter and rank on answers.<j>.interval.lower and .upper. See automate on the bound.
  • Nothing is rewritten. When the correction changes, each read uses the new one at once, and Brussle brings the judgments that read this one up to date, billed, without a confirm. This happens only when the fit p reads changes; a pin or a promote is confirmed first. See events when the fit changes and roll-ups.
  • Your thresholds on p. You chose a threshold against the engine’s p. Once p is corrected, the same threshold selects different documents, and GET on the judgment shows a fit_changed warning until you set a threshold again. The threshold recommender works from the corrected p.
  • Templates. Each tenant reads the template’s pooled correction until it has enough outcomes of its own. See per-customer corrections.

Read each feature

In each epoch of the report, held_out_metrics.features gives the fit with features and reads each feature on its own, on the same held-out outcomes:
  • engine_weight: how much the fit weighs the engine’s answer. It is never negative, so for two answers with the same features, a higher raw p never reads lower.
  • Entries come in the order of the definition.
  • weight: how strongly one standard deviation of the feature moves p, so you can compare features in different units. Positive raises p; negative lowers it; near 0 means little importance. Here, more seats make a cancellation less likely, and older accounts a little more likely.
  • center and scale: the feature’s average and standard deviation over the outcomes the fit used. A missing value counts as center.
  • missing: the share of those outcomes whose answer had no value. A feature that is often missing can say little.
  • without.log_loss: the held-out log loss of the fit made again without this feature. Here, without seats the loss goes from 0.31 to 0.37.
  • alone.log_loss: the held-out log loss of a fit on this feature alone, without the engine’s answer.
  • warning: reads_like_the_outcome, or absent.
The judgment’s Calibration section shows the same list under With features, largest weight first.

A feature that reads like the outcome

A number written once the outcome is known, such as a refund amount or a cancellation date, predicts the outcome almost perfectly. A fit on it looks very good on your past outcomes, but when an answer is needed the number is not there yet, and the fit is worth nothing. Brussle cannot know when you write a field, but it can see the result: when a feature alone scores below a tenth of the log loss of always answering base_rate, its entry has "warning": "reads_like_the_outcome", and the Calibration section shows reads like the outcome.
  1. Check when your system writes that field.
  2. If it is written once the outcome is known, post a new version without the feature.
The warning is about your data. It never changes which fit answers read: Brussle chooses the fit on held-out outcomes only.

Drop a feature that adds nothing

A feature whose without is not higher than the fit’s own log_loss adds nothing the engine and the other features do not already give. A weight near 0 with without equal to log_loss marks a candidate. Drop it in a new version, and keep the budget of 12 for numbers that matter. Compare the new version’s report with the old one before you activate it, or run a shadow report.

Features on a choice or a score

A choice and a score take features as a bool does: the same sources, the same limit of 12, the same rules. Their fit weighs the features beside the engine’s whole distribution.
  • The answer’s numbers are the corrected ones: dist, escape_p and value on a choice; dist and score on a score. raw holds the engine’s: {dist, value, escape_p} or {dist, score}. There is no calibrated object, and fit names the correction as on a bool.
  • The pick can change. value is the most probable option of the corrected dist. With features, the fit can make another option, or none_of_the_above, the most probable. Without features, the order among your options never changes: the fit only moves answers toward or away from none_of_the_above.
  • Every reader uses the corrected numbers: thresholds on dist, filters and ranking on escape_p, score, value and dist.<option>, subscriptions, the judgments that read it and links. A new fit that changes a pick moves a link to the new document and brings its readers up to date, billed.
  • fit.interval is on escape_p for a choice and on score for a score. Filter and rank on its bounds.
  • Chosen like any fit: only when it beats the engine’s numbers and the fit without features on outcomes it did not use.
  • In the report, held_out_metrics scores the whole distribution, with accuracy (and on a score, mean_level_distance). On a fixed choice, per_feature[].by_option gives each feature’s weight on each option, and moved_picks the share of picks the fit changes.

Features on a choice among candidates

A choice among candidates (options.from) can keep two kinds of feature:
  • Numbers about the judged document, such as candidates.count or the payment’s state.amount. They help decide whether a match exists, which is 1 - escape_p.
  • Numbers about each candidate, <from>.each(<path>), such as candidates.each(state.open_amount) or candidates.each(age(created_at)). They help decide which candidate it is. <path> is the candidate’s state.<path> or attributes.<name>, or age(created_at), age(updated_at) or age(<state or attributes path>) of the candidate, as of the judged document’s revision. Each candidate the engine read keeps its numbers under the answer’s candidate_features, by candidate id. They count toward the 12.
How they act:
  • A candidate the engine gave no probability stays at none. The fit only weighs the candidates on the engine’s shortlist.
  • An answer with no candidates stays none_of_the_above, with escape_p 1, whatever the fit.
  • Without numbers about candidates, the engine’s order among them is kept. With them, a candidate your numbers favour can rise above one they do not, at equal engine confidence: the invoice with nothing paid against it can rise above one already settled.
  • Never a worse pick. The fit is used only when, on outcomes it did not use, its pick is right at least as often as the engine’s. A pick is right when it names the true match, or the escape where there was no match. When numbers about candidates would make it pick worse, Brussle fits again without them, keeping the engine’s order.
  • A candidate’s answers cannot be a feature. candidates.each(answers.<judgment>.<part>) is invalid_request: through a link, it would read the matcher’s own pick back into its next answer. Keep the number on the candidate’s state and name that.
  • Not under the promotion rule yet: each run’s fit replaces the current one.
In the report, held_out_metrics reads whether a match exists for the four readings, with the ranking, and held_out_metrics.candidates gives the rest:
  • joint: the held-out log loss of the whole answer (the escape and every candidate): raw, with the fit without features, and with the fit with features. Brussle chooses on it.
  • selection: how often the pick was right, raw (raw_accuracy) and corrected (fitted_accuracy), over every outcome, so a fit that rightly says there is no match counts as picking right. guard.state is fitted, kept_engine_order (with both accuracies before the fit was made again), or too_few_matched (fewer than 20 outcomes name a match among two or more candidates, so the engine’s order holds). accuracy_holds is true when the fit picks at least as well.
  • moved_picks: the share of the outcomes the fit used whose pick it changes. It is a number to read, not a condition.