Branches and absence
A workflow does not always receive every value. A person leaves an optional note empty; an order has no coupon; a
filter decides that the gift branch is not needed. Madoo does not invent a value in those cases — no empty string,
no null, no zero. The value is absent, and each node that expected it either works without it or is
skipped. A skipped node costs nothing, and the run still completes.
What decides between the two is why the value is missing.
Two kinds of absence
Section titled “Two kinds of absence”| Absence | Meaning | Where it comes from |
|---|---|---|
| Not provided | there is no value — work without it if you can | an optional input left empty; an output a node produces only on request |
| Rejected | a decision cut this branch — it must not happen | a Filter that rejects; Extract with When Missing absent and the field missing; Select by Key with no match; a Quality Gate in route mode that rejects |
A not provided value is a gap the node may be able to fill. Image Transform (ai/image_transform) with one
reference image out of three runs with that one; a Template with an empty placeholder writes the text without it.
The node is skipped only when the value was needed — when it lands on a required port.
A rejected value is a decision. When a Filter rejects the product, the author does not want its description
written, its image generated or its price computed, whatever port the value enters. So a node that receives a
rejected value is skipped on any port, required or optional, and records the reason BRANCH_REJECTED. Its own
outputs are then rejected too, so the next node skips for the same reason, and the next: a rejected branch
never runs anywhere downstream, however deep it goes, and never spends a credit.
That is the difference that matters when you design a graph. Leave a value not provided when a step can do without it; reject it when the step — and everything after it — should not happen.
| The node receives… | on a required port | on an optional or dynamic port |
|---|---|---|
| a value | runs | runs |
| nothing, not provided | skipped (REQUIRED_INPUT_ABSENT) |
runs without it |
| nothing, rejected | skipped (BRANCH_REJECTED) |
skipped (BRANCH_REJECTED) |
Inputs that receive a cut branch
Section titled “Inputs that receive a cut branch”A few nodes exist precisely to deal with branches that may have been cut, and keep running when one is:
- Combine Conditions (
utility/boolean_combine), Select by Key (utility/select_by_key), Quality Gate (utility/quality_gate) and Dubbing Manifest (utility/dubbing_manifest) accept a rejected value on every input, dynamic ones included — they decide about the branches, so a cut one is information, not a stop. - Duck (
audio/duck) accepts it on Background: when the music branch was cut, the voice passes alone. - Aggregators (
aggregate/*) collect branches: a rejected item is a hole in the collection, not the end of it.
The catalog marks them: accepts_rejected on a port, accepts_rejected_inputs on a node (get_node_type, the
REST catalog and MCP all show it). Every other node skips.
Combine Conditions still does not guess a missing condition: when one is absent it skips, and when that condition was rejected it hands the rejection on, so the branch after it stays cut.
Skipped is not failed
Section titled “Skipped is not failed”A skipped node is part of a successful run. When every node ends completed or skipped, the run is
completed; partial_success means that a node really failed.
In the result, an output on a skipped branch has "state": "absent" and no value: the key is there, so the caller
knows which output did not happen. Read state before value or url. The execution trace
(GET /api/v1/executions/{id}/trace) lists each node’s status and, for a skipped one, its skip_reason:
skip_reason |
The node was skipped because… |
|---|---|
optional_input_not_provided |
it is an optional input and nobody filled it |
required_input_absent |
a required port received nothing (not provided) |
branch_rejected |
a rejected value reached it, on any port |
upstream_absent |
it is an output node and its source produced nothing |
The lab
Section titled “The lab”The branches lab shows both kinds of absence in one run of a thank-you message for an order. It reads the customer’s name and the coupon code from the order, with the coupon read as absent when it is missing; an optional personal note feeds the greeting and a Predicate that says whether a note was written; and a Gift wrap switch drives a Filter in front of a gift card and its label, two nodes deep.
The reference run leaves the note empty, turns the gift wrap off, and sends an order without a coupon. In the figure, the skipped nodes are grey and dashed, with their skip reason.
| Node | What happened | Why |
|---|---|---|
| Personal note | skipped, optional_input_not_provided |
left empty |
| Greeting | ran: Thank you, Giulia Ferri. |
the note enters an optional placeholder — not provided, so the Template works without it |
| A note was written | skipped, required_input_absent |
the Predicate’s Value port is required |
| Only for gifts | ran, rejected the name | the switch is off |
| Gift card, Gift label | skipped, branch_rejected |
the rejection reaches the card, and the card’s skip reaches the label |
| Coupon code | ran, emitted absent | the order has no coupon, and When Missing is absent |
| Coupon line | skipped, branch_rejected |
a missing field read as absent is a rejection |
The run completes in a few seconds at no cost, with one output present and three absent. With a note, a coupon and
the gift wrap on (run-inputs-everything.json),
every node runs and all four outputs are present: Thank you, Giulia Ferri. Enjoy the lamps!, note_given: true,
Label: A gift for you, from Giulia Ferri and Coupon applied: SPRING10.
node madoo-install-example.mjs lab-branches --api <your Madoo API URL> --key <key.json> --runDesigning with absence
Section titled “Designing with absence”- Optional inputs for optional content. A note, a logo, a second reference image: leave the port optional and the node works without them. No Filter is needed.
- A Filter for optional steps. An option such as add subtitles or gift wrap is a Boolean Input and a Filter in front of the first step of the branch; everything after it follows.
- A default when the branch must go on. Extract with When Missing default keeps the branch running with a value you choose; absent cuts it; fail stops the run. Pick the one that says what a missing field means.
- Check first, spend later. Put the cheap check — a Predicate, a Quality Gate — before the AI step. A cut branch never reaches the model.