Deciding
A decision in a workflow has three parts, each a small node that costs nothing:
- Predicate (
utility/predicate) compares values and answerstrueorfalse: is the stock above 0? - Combine Conditions (
utility/boolean_combine) joins several answers: all checks pass, or someone approved it by hand. - Filter (
utility/filter) lets a value through when the answer istrueand rejects it otherwise: the branch that needed the value then does not run.
A decision never reaches an AI model as a question. The data is read with Extract, compared with Predicates, and the AI step runs only on the branch that passed — which is how a workflow checks something cheap before spending credits on something expensive.
Predicate: one comparison
Section titled “Predicate: one comparison”A Predicate compares its input Value (a) with a second value, and answers with a boolean result.
| Operation | True when | Types |
|---|---|---|
equals, not_equals |
the two values are (not) equal | two numbers, two texts, two booleans, or null |
greater_than, greater_or_equal, less_than, less_or_equal |
the order holds | two numbers |
contains, starts_with, ends_with |
the text contains / starts with / ends with the other | two texts, compared exactly (case counts) |
is_null, is_not_null |
the value is (not) JSON null |
any; no second value |
The second value comes from the Compare With port (b) when something is connected to it, otherwise from the
parameter of the matching type: Compare Number for the order operations, Compare Text for the text
operations, and, for equals, the parameter that matches the type of the value — Compare Number for a number,
Compare Text for a text, Compare Boolean for a boolean. Connect Compare With when the threshold changes from run to
run (a Number Input, a field of the data); set the parameter when it is fixed. When an optional input connected to
Compare With is left empty, the parameter applies.
Comparisons are strict: the text "85" is not the number 85, and equals between a number and a text is an
error (PREDICATE_TYPE_MISMATCH), not false. Set Extract’s expected type so each value arrives with the type the
comparison needs.
is_null asks whether a value is JSON null — a field that exists and is empty. It does not detect a field that is
missing: for that, read the field with Extract set to absent or default and use its found output, which is a
boolean ready to combine.
Combine Conditions: several decisions
Section titled “Combine Conditions: several decisions”Combine Conditions takes two to sixteen booleans on conditions_0, conditions_1, … and answers:
- All (
mode: all) — true when every condition is true; - Any (
mode: any) — true when at least one is; - Invert Result turns the answer around, for none of (
any+ invert) or not all (all+ invert).
Nest them for longer logic, and keep each level readable on the canvas: (in stock AND rated well AND brand safe)
OR manual override is one all node feeding one any node, not a formula. Every condition is computed — there is
no short-circuit — so put an expensive check behind a Filter rather than inside a combination.
A condition that does not arrive is not guessed: when any condition is absent, the node is skipped instead of deciding with a missing input.
When the decision needs a model
Section titled “When the decision needs a model”Predicate compares values that already exist. When the condition is about content — is this photo blurred, does this message ask for a refund, is this document an invoice — ask it with Decide with a Model: it answers with a boolean, a value or a score and a probability on ports named after the question, and the nodes of this page route on them exactly as on a Predicate’s result.
Filter: the decision chooses what runs
Section titled “Filter: the decision chooses what runs”A Filter receives a condition and a value. When the condition is true it passes the value on unchanged — the
same type, the same file, the same text; when it is false it emits the value as rejected. Invert makes it
pass on false. Its passed output repeats the decision as a boolean.
A rejected value stops the branch it feeds: every node after it is skipped, whatever port the value enters, and costs nothing. The workflow’s outputs on that branch are reported as absent, and the run still succeeds.
Two Filters on the same condition, one inverted, make an if/else: exactly one of the two values passes, and only its branch runs. The workflow then has one output per branch — one of them present, the other absent — and the caller reads which one arrived.
The condition must be a real boolean: the text "true" is refused (INVALID_CONDITION_TYPE). Connect a Predicate,
Combine Conditions, a Boolean Input, a found output or a boolean field read with Extract.
The lab
Section titled “The lab”The decisions lab checks a product before publishing it. It reads the name, the stock, the rating and a brand safe flag from the product; two Predicates compare the stock with 0 and the rating with a minimum that comes from an input; one Combine Conditions requires all three checks, a second lets a manual Publish anyway switch override them; and two Filters make the if/else — a listing line when the product passes, a hold note when it does not.
| Oak chair, stock 12, rating 4.6 | Linen cushion, stock 0, rating 4.8 | |
|---|---|---|
In stock (stock > 0) |
true |
false |
Rated well enough (rating >= 4) |
true |
true |
| All checks pass | true |
false |
| Checks pass or override | true |
false |
| Listing line | Now available: Oak chair |
absent |
| Hold note | absent | On hold for review: Linen cushion |
Both runs complete in about ten seconds at no cost; in each, the branch that was not taken is skipped. Turn Publish anyway on and the cushion is published despite its stock.
node madoo-install-example.mjs lab-decisions --api <your Madoo API URL> --key <key.json> --runThe second product is in run-inputs-out-of-stock.json.
Errors
Section titled “Errors”| Code | Cause | Fix |
|---|---|---|
PREDICATE_TYPE_MISMATCH |
the two values have different types, an order operation received a text, or the comparison value is missing | give both sides the same type (Extract with an expected type); set the typed parameter or connect Compare With |
INVALID_PREDICATE_OPERATION |
the operation is not one of the eleven | pick one from the table |
MISSING_LEFT_INPUT |
nothing is connected to Value | connect the value to compare |
INVALID_CONDITION_TYPE |
a Filter condition is not a boolean (for example the text "true") |
connect a boolean producer |
INVALID_BOOLEAN_CONDITION |
a Combine Conditions input is not a boolean | connect booleans only |
MIXED_CONDITION_INPUT_FORMS |
a boolean array and single conditions are connected together | use one form |
INVALID_BOOLEAN_COMBINE_MODE |
Mode is neither all nor any |
choose all or any |