Skip to content

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.

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)

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.

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 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.

The lab, left to right: the order, the optional note and the gift switch; the customer name and the coupon code; the greeting, the Predicate on the note, the gift Filter and the coupon line; the gift card; the gift label. The note, the Predicate, the coupon line, the gift card and the gift label are skipped

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.

Terminal window
node madoo-install-example.mjs lab-branches --api <your Madoo API URL> --key <key.json> --run
  • 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.