Skip to content

Keeping state in a flow

Need a running total or a last-seen value? Make it a message. Function nodes have no hidden context or global flow store.

Read and write the same message

Declare reading and total as inputs, and total as an output:

def process(reading, total):
    return {"total": total + reading}

Add total to the flow's Inputs with a starting value of 0.0. Each execution reads the previous total and publishes the next one.

  • A node is never downstream of itself, so its own publication does not run it again.
  • A triggering input needs a starting value. Without one, validation reports self_loop_needs_initial; a Python default alone does not supply the message.

Feedback between two nodes

A controller may read an estimate that depends on its own previous output. Ordinary triggering inputs would form a cycle. Mark the feedback input non-triggering in the flow definition instead:

{"name": "estimate", "port": "estimate", "dtype": "float", "trigger": false}

It creates no dependency and does not wake the node. Whenever another input runs the node, it reads the latest estimate. If no estimate exists yet, that argument is omitted, so give it a Python default:

def process(reading, estimate=0.0):
    return {"command": reading - estimate}

Use this on the feedback input; keep the inputs that should wake the node triggering. Configure trigger through the API or flow JSON; the node panel does not currently expose it.

Built-in state

Built-in nodes such as delay, rate limit and filter-on-change keep their own timing or comparison state in the engine. Storage and device nodes also manage the state needed for their connections. Your Function code still exchanges state through declared messages.

See also