Skip to main content
The examples on this page use one decision, refund-exception, from an online store’s support app: should a refund request outside the store’s standard return policy be granted, escalated to a person, or denied? How Moonbounce works introduces it.
An aggregation is a feature Moonbounce computes from other features: questions, attributes, or other aggregations. None of them learns anything. Each does exactly what its definition says, with every weight, threshold and cut point written in your structure and hashed with it. Describe a decision builds the refund decision with two of them; this page lists all eleven and how to choose.

Four kinds

Every type but the outcome ones produces a number, so they nest freely: a check can feed a probability, an average can feed a check, and any of them can be the decision’s result. An outcome aggregation produces a name, so only rules and match can read it.

Shared rules

  • Numeric and check aggregations list the features they read in inputs: [{ "name": … }], with that type’s per-input fields next to the name. bands and match read a single feature, named in input; rules read the features named in their conditions.
  • A reference is the feature’s path: questions.<name>, attributes.<name> or aggregations.<name>, in inputs, input, rule conditions and result alike. A bare name, or a path under the wrong root, is a 400 that says which path to write.
  • invert: true reads an input as 1 − x, a “not”. logistic uses a negative weight instead.
  • The order of inputs doesn’t matter: reordering them changes neither the hash nor the value. Evaluation uses the same canonical input order as hashing, including for floating-point sums and products.
  • Non-finite numbers, question answers outside 0 to 1, and numeric overflow become unknown. A rule can handle an unknown input explicitly with abstained: true.
  • An input that names a 0-to-1 type must be a 0-to-1 value: a question, a boolean attribute, a number attribute declared with minimum: 0 and maximum: 1, or another number-valued aggregation.

Weighted averages

weighted_mean computes Σ wᵢ·xᵢ / Σ wᵢ. Use it for anything where you decide how much each feature counts: quality rubrics, ranking, risk indexes, rewards.
  • Weights are relative. 0.5, 0.3, 0.2 means the same as 5, 3, 2. Give every input a weight, or none for a plain mean. The calculation scales the known inputs’ weights together to avoid overflow and tiny-weight underflow.
  • Weights are positive. Direction comes from invert.
  • It is not a probability. If is_helpful is 0.9 and is_concise is 0.3, their average of 0.6 is not the chance that anything happens. That is what a rubric needs.
  • An average can’t veto. An unsafe answer with a small weight still rates high if everything else is good. Gate it with min, as in A gate on an average.

Probabilities

These read 0-to-1 values and output the probability of a statement.

Choosing min, max, product or noisy_or

product and noisy_or assume the inputs are independent; min and max assume they overlap as much as possible. The true answer always lies between the two. Use product and noisy_or for unrelated checks, such as spam and abuse. Use min and max for several questions about the same thing. With correlated inputs, product keeps dropping as checks are added, and noisy_or counts the same evidence more than once.

logistic

The place for fitted numbers: the coefficients and intercept from a logistic regression you trained on labeled data paste in directly.
  • Weights are signed and absolute. A negative weight lowers the result, so there is no invert.
  • bias and every weight are required. bias sets the result when every input is 0; without one, no evidence would always give 0.5.
  • Inputs are raw values, not log-odds, so logistic also reads number attributes outside 0 to 1, such as an account’s age in days.
Without a fit, use one of the other types: a guessed logistic looks more precise than it is.

Checks

all and any compare each input to a threshold and output 1 or 0. Because a check produces a number, it nests anywhere: inside a probability, inside weighted_mean as the share of checks passed, or as the result.
  • The default threshold is ≥ 0.5. A bare entry means “at least 0.5”, which for a yes/no question reads as “more likely yes than no”. Writing "gte": 0.5 hashes the same as leaving it out.
  • Other thresholds use gte (≥), gt (>), lte (≤) or lt (<), one per entry, within the feature’s range. A number outside 0 to 1 has no default, so it always names one.
  • Thresholds are part of the structure. Changing one is a new version, like changing a question.

Outcome aggregations

bands cuts one number into ranges, rules names an outcome from an ordered list of conditions, and match maps a label straight to an outcome. From answers to an outcome covers all three with the refund tickets, and what each puts in the response’s outcome, value and via. match refuses the case key __proto__. To route that exact label, use rules with an is condition instead.

What can read what

Missing answers

A question with no answer, because the engine abstained, is unknown, and so is any aggregation that can’t be computed without it:
  • weighted_mean leaves the input out, averages the rest and lists it in missing. It is unknown only when nothing is left.
  • logistic, min, max, product and noisy_or are unknown when any input is.
  • all is false as soon as a check fails, and unknown if none failed but an answer is missing. any is true as soon as a check passes, and unknown if none passed and one is missing.
  • bands and match give their abstain outcome, or are unknown.
  • rules stop at the first rule that is true or unknown; an unknown one makes the result unknown. A rule with { "name": "…", "abstained": true } catches it first.
An unknown result makes the evaluation’s status abstained. Whatever the status, the evaluation’s top-level missing lists every question the result depends on that has no answer, so you can tell a decided result that used every answer from one that went without some. It is absent when nothing is missing.

Validation

The structure is checked before anything runs. The graph may not loop: an aggregation that reads itself, directly or through others, is a 400 whose message names the cycle, such as aggregations form a cycle: quality → reward → quality. The full list of rules is on Describe a decision.

Patterns

Group reasons, then weigh the groups

The refund decision’s carrier_delay question, “Did the carrier deliver the order late, or lose it?”, is the fix the loop finds for ticket C, the late tent. Added as a sixth input of the plain average, it lifts C from 0.540 to 0.612, still escalate under the 0.72 grant cut. And it says “no” on almost every other ticket, so it drags them all down by about a sixth: ticket A, the ripped zipper, falls from 0.766 to 0.642 and loses its grant. The policy is really “any failure of ours is a reason to grant”, weighed against everything else. A defect and a late carrier are two independent ways that can happen, and a ticket needs only one. So group them with noisy_or, group the two reasons for caution with max, and weigh the groups:
A’s our_fault is 0.961 because of its defect, and C’s is 0.972 because of its carrier, so the “no” on the other question barely counts against either ticket. The response shows every aggregation’s value, so it explains the outcome one level at a time. Group features because they share a reason, and check that the weights that follow still make sense: caution as a whole now weighs as much as outside_window alone.

A gate on an average

An average can’t veto, so put a min on top. Here an RL reward is a weighted rubric, held down whenever the response is unsafe, however good it is otherwise:
The result is the reward value itself, with status computed and no outcome.

Let one strong yes decide

An average dilutes: in a three-input mean, one input moves the value by at most a third. When any single reason should be enough, use max (the reasons overlap) or noisy_or (they are independent). A camp stove that caught fire, with legal_threat 0.02, safety_issue 0.97 and public_complaint 0.03: noisy_or adds up weak evidence; max never does.

Rules that mix attributes and answers

An attribute your app supplies combines with a question in one condition. A stricter spam policy in the EU:

Try a question before it counts

A question that no aggregation reads, and that isn’t the result, is observe-only: the engine answers it and the evaluation records it, but it never moves the outcome, and its failure never fails the call. Add a question that way, watch its answers on real traffic, and wire it in once it answers the way you expect.

Next

From answers to an outcome follows the refund tickets through bands, and covers rules, match and the status.