Publish the documentation site: docs.fluksio.com
A zensical site under docs/, served by a new `docs` compose service behind Traefik, built with --strict in CI. Same pattern the sibling n3xd workspace uses. Getting started splits the way the landing page does — one path is `pip install fluksio` and a training script, the other is a Docker stack and an afternoon in the browser — because the two audiences will not spend the same amount of time. Everything after that is shared: the concepts, the web interface (app and portal), the CLI and the API, and a reference for node types, payload types and configuration. The three flow guides move here from the docs submodule rather than being copied, so there is one version of them. Styling mirrors DESIGN-GUIDELINES.md: the app's token palette remapped onto Material's variables in both schemes, Inter, the 16px panel radius, and the one terracotta accent spent on the facility lane of the audience split. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M7Xv3cJEW5c8AXxn2hoojV
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
# Keeping state in a flow
|
||||
|
||||
Logic nodes are pure functions of their inputs. There is no `context`, no
|
||||
`global`, no handle to a store — a node is called with the values of the
|
||||
messages it declares and returns the values of the messages it provides.
|
||||
|
||||
That is deliberate: a node with hidden state cannot be run twice in parallel,
|
||||
cannot be replayed, and cannot be moved to another worker. But plenty of real
|
||||
automations need to remember something. This is how.
|
||||
|
||||
## State is a message the node both reads and writes
|
||||
|
||||
A running total, a debounce timer, a last-seen reading — each is a value that
|
||||
survives between runs. Give it a message name, declare it as both an input and
|
||||
an output, and it is state:
|
||||
|
||||
```python
|
||||
def process(reading, total=0.0):
|
||||
return {"total": total + reading}
|
||||
```
|
||||
|
||||
with `reading` and `total` as inputs and `total` as the output. Each run reads
|
||||
the total the previous run wrote.
|
||||
|
||||
Two things make this work rather than loop forever:
|
||||
|
||||
- **A node never depends on itself.** The graph is built from message names, but
|
||||
a node providing a message it also requires is not placed downstream of
|
||||
itself. Publishing `total` does not re-run the node that wrote it.
|
||||
- **The value must start somewhere.** The first run has nothing to read. Declare
|
||||
the message as a flow input with a starting value; otherwise the flow reports
|
||||
`self_loop_needs_initial` at edit time and refuses to publish.
|
||||
|
||||
The port needs a default in the function signature (`total=0.0` above) so the
|
||||
first call is well-formed even if the value is missing.
|
||||
|
||||
## Feeding a value back between two nodes
|
||||
|
||||
Sometimes the value comes from a different node — a controller reading back what
|
||||
an estimator computed from its own last output. Written plainly that is a cycle,
|
||||
and the validator rejects it, because a graph where A waits for B and B waits
|
||||
for A can never start.
|
||||
|
||||
What is actually meant is that the fed-back value is *state*, not a trigger.
|
||||
Say so, by marking the input non-triggering:
|
||||
|
||||
```json
|
||||
{"name": "estimate", "port": "estimate", "dtype": "float", "trigger": false}
|
||||
```
|
||||
|
||||
A non-triggering input:
|
||||
|
||||
- creates no dependency, so it cannot form a cycle;
|
||||
- never makes the node wait — if the message has no value yet, the port is
|
||||
simply left out of the call, and the function's default applies;
|
||||
- is read fresh from state whenever the node does run, for whatever reason.
|
||||
|
||||
Use it for the back edge only. An input that should wake the node is an ordinary
|
||||
input, and marking it non-triggering would mean the node never runs at all.
|
||||
|
||||
## What still holds engine-side state
|
||||
|
||||
Built-in nodes that are *about* time or change — rate limiting, filter-on-change,
|
||||
delay, cron — keep what they need in the engine's own state backend, under keys
|
||||
that never appear as messages. That is the engine's business, not a flow's: the
|
||||
line is that node *code* you write never reaches for state, while node *types*
|
||||
the engine ships may.
|
||||
|
||||
Storage and direct-I/O nodes are the other sanctioned exception, since talking
|
||||
to a device or a database is stateful by nature.
|
||||
|
||||
## See also
|
||||
|
||||
- [Where a node's values come from](values.md) — ports, settings and flow inputs
|
||||
- [Node types](../reference/node-types.md) — the built-ins that do keep engine-side state
|
||||
Reference in New Issue
Block a user