bool, a choice and a score can all have features, and a judgment does not need relations to have them.
Add features
Name the features infeatures. The renewal_risk judgment reads an account’s notes and keeps three features (the account’s seats, the days since it signed, and the engine’s previous answer).
state.seatsis the number on the account itself.age(attributes.signed_at)is the days sincesigned_at, measured from the account’s ownupdated_atat the revision Brussle judged.previous.pis the engine’s ownpthe last time it judged the account, and is missing for the first answer.
features. When a write changes only a feature (such as state.seats), Brussle records the answer again with no engine call and no charge. A judgment with an age feature does the same for a write of the same content, because the age moved. Once you have 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, so adding, removing or renaming a feature makes a new version with its own fit. Once you activate the new version, the earlier version’s answers read stale. The 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).
- Brussle measures each age from the judged document’s own
updated_atat the revision it judged, never from a related document’s write or the clock. So a write to a related document never moves an age, while 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 (seconds since 1970, say) or any other text reads as missing. - A time later than the judged document’s
updated_atgives a negative age. - Brussle refuses
age(updated_at), since at the judged revision it is always 0. Name the time you mean instead, such asage(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. - It stays the same when only a feature moves (see reuses).
- Outcomes replayed after the engine changes read it as missing, because their earlier answers came from the other engine.
invalid_request when its path and a path an outcome rule reads are the same, or one is inside the other (for example state.refund.amount and a rule on state.refund), because such a feature would read the outcome itself. Brussle checks this against every version of the judgment, both when you create a version and when you set rules. It does not check a rule’s path on a related document.
Ask about what will happen
The engine answers well about what a document shows now, but near chance about what will happen. So for a question about what will happen:- Ask a narrow question about what the document shows now.
- Keep counts from related documents as
features, with"render": falseso the engine does not read them. - Set a
horizon, and record what happened as outcomes. - Let the fit on your outcomes weigh the engine’s answer and the counts.
goes_overdue 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). Its outcome records whether the invoice went overdue within 30 days.
held_out_metrics in the report 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
calibratedobject, becausepis the selected correction, applied when the answer is read.raw.pis the engine’s own answer, whose meaning never changes.fitsays which correctionpused, withid,interval,run,pinned,fitted_at,outcomes,methodandfrom_previous_epoch, and isnullwhilepis the engine’s.methodisfeatureswhen the fit uses the features, andearlyorfullwhen it corrects only the engine’sp.fit.intervalis the 95% interval ofpat this answer’s features. - 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. Brussle chooses the fit with features only when it beats both the engine’s
pand the other fit on outcomes it did not use, and a fit is never tested on a document whose other outcomes it saw. If neither fit beats the engine,pstays the engine’s. Below 100 outcomes Brussle reports the fit and never applies it (readers_need_full). - Labels must cover the range. A fit with features made on labels from one part of the range, such as the documents a review queue sent you, learns that part: inside it the engine’s answers barely differ, so the fit leans on the features and gives the engine’s answer almost no weight, and applied to the rest of the range it is wrong exactly where a threshold acts. When the labels leave more than 20% of the live answers outside their range, no correction is applied (
truncated_sample): answers read the engine’s number until the labels cover the range, because below the labels even the correction on the engine’s answer alone is a guess. Label a random sample, or use the labelling queue. On skewed traffic the queue’s labels count for fewer than there are, and a fit with features needs more of them than a fit on the engine’s answer alone: see what the even half costs. - All readers use
p. Thresholds, filters, ranking, subscriptions and the judgments that read it use the correctedp(on achoiceorscore, the corrected numbers). A threshold on a feature still reads the stored feature, though. Groups cannot count a judgment with features, and the evaluation log and the export keep the engine’spand the features. - Automate on the bound. Queries and subscriptions can filter and rank on
answers.<j>.interval.lowerand.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. That happens only when the fit
preads changes. A pin or a promote is confirmed first. See events when the fit changes and roll-ups. - Your thresholds on
p. You chose each threshold against the engine’sp, so oncepis corrected the same threshold selects different documents.GETon the judgment shows afit_changedwarning until you set a threshold again, and the threshold recommender works from the correctedp. - Templates. Each tenant reads the template’s pooled correction until it has enough outcomes of its own (see per-customer corrections).
Read each feature
Each epoch of the report gives the fit with features inheld_out_metrics.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 rawpnever reads lower.per_featurelists its entries in the order of the definition.weight: how strongly the feature movesp, on one scale for all features, so you can compare features in different units. A positive weight raisesp, a negative one lowers it, and a weight near 0 means little importance. In the example, more seats make a cancellation less likely, and older accounts make it a little more likely.centerandscale: the feature’s average and standard deviation over the outcomes the fit used. A missing value counts ascenter.missing: the share of those outcomes whose answer had no value, so 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. In the example, the loss goes from 0.31 to 0.37 without seats.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.
A feature that reads like the outcome
A number written once the outcome is known (a refund amount, a cancellation date) predicts the outcome almost perfectly. A fit on it looks very good on your past outcomes. But the number is not there yet when an answer is needed, so 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 answeringbase_rate, its entry has "warning": "reads_like_the_outcome", the epoch’s warnings carry reads_like_the_outcome naming it, and the Calibration section shows reads like the outcome.
The engine’s own answer gets the same test, on held_out_metrics.raw. An engine that reads a document changed to show the outcome, such as a note that the customer gave notice written before the document was judged again, predicts the outcome as perfectly as a leaked feature, and the warning names engine.
- Check when your system writes that field, and when it changes the document the engine reads.
- If a feature is written once the outcome is known, post a new version without it.
- If the document changes once the outcome is known, date each labelled example when the answer was acted on, or use an outcome rule. See post labelled examples.
labels_read_like_the_outcome. See why an epoch has no correction.
Drop a feature that adds nothing
A feature whosewithout 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 to 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
Achoice and a score take features as a bool does (the same sources, the same limit of 12, the same rules), and their fit weighs the features beside the engine’s whole distribution.
- The answer’s numbers are the corrected ones:
dist,escape_pandvalueon achoice, anddistandscoreon ascore.rawholds the engine’s numbers ({dist, value, escape_p}or{dist, score}). There is nocalibratedobject, andfitnames the correction as on abool. - The pick can change.
valueis the most probable option of the correcteddist, so with features the fit can make another option, ornone_of_the_above, the most probable. Without features the order among your options never changes, since the fit only moves answers toward or away fromnone_of_the_above. - Every reader uses the corrected numbers: thresholds on
dist, filters and ranking onescape_p,score,valueanddist.<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.intervalis onescape_pfor achoiceand onscorefor ascore. 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_metricsscores the whole distribution, withaccuracy(and on ascore,mean_level_distance). On a fixedchoice,per_feature[].by_optiongives each feature’s weight on each option, andmoved_picksthe 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.countor the payment’sstate.amount, help decide whether a match exists (1 - escape_p). - Numbers about each candidate,
<from>.each(<path>), help decide which candidate it is (for examplecandidates.each(state.open_amount)orcandidates.each(age(created_at))).<path>is the candidate’sstate.<path>orattributes.<name>, orage(created_at),age(updated_at)orage(<state or attributes path>)of the candidate, as of the judged document’s revision. The answer keeps the numbers of each candidate the engine read undercandidate_features, by candidateid, and they count toward the 12.
- A candidate the engine gave no probability stays at none, since the fit only weighs the candidates on the engine’s shortlist.
- An answer with no candidates stays
none_of_the_above, withescape_p1, 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. Brussle uses the fit only when its pick is right at least as often as the engine’s on outcomes it did not use. A pick is right when it names the true match, or the escape where there was no match. When numbers about candidates would make the fit pick worse, Brussle fits again without them and keeps the engine’s order.
- A candidate’s answers cannot be a feature. Brussle refuses
candidates.each(answers.<judgment>.<part>)withinvalid_request, because through a link it would read the matcher’s own pick back into its next answer. Keep the number on the candidate’sstateand name that instead. - Not under the promotion rule yet, so each run’s fit replaces the current one.
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.stateisfitted,kept_engine_order(with both accuracies before the fit was made again), ortoo_few_matched(fewer than 20 outcomes name a match among two or more candidates, so the engine’s order holds).accuracy_holdsistruewhen the fit picks at least as well.moved_picks: the share of the outcomes the fit used whose pick it changes. Brussle sets no condition on it; it is there for you to read.