Make the docs state things rather than argue them
Docs / docs (push) Successful in 37s
Playwright Tests / test-playwright (1, 2) (push) Failing after 1m35s
Playwright Tests / test-playwright (2, 2) (push) Failing after 17s
pre-commit / pre-commit (push) Failing after 2m8s
Test Backend / test-backend (push) Failing after 2m48s
Compose Smoke Test / test-compose (push) Failing after 13s
Playwright Tests / merge-reports (push) Failing after 2m25s
Docs / docs (push) Successful in 37s
Playwright Tests / test-playwright (1, 2) (push) Failing after 1m35s
Playwright Tests / test-playwright (2, 2) (push) Failing after 17s
pre-commit / pre-commit (push) Failing after 2m8s
Test Backend / test-backend (push) Failing after 2m48s
Compose Smoke Test / test-compose (push) Failing after 13s
Playwright Tests / merge-reports (push) Failing after 2m25s
The site read as a design journal: rationale paragraphs, hedges
("deliberately", "on purpose", "genuinely"), meta-commentary about the docs
themselves, and one em-dash every ten lines carrying an aside.
Roughly twenty rationale blocks are gone or reduced to what a reader needs
in order to use the thing. Em-dashes go from 507 to 135, and what is left is
structural rather than prose: list and definition separators, table cells,
and four inside code blocks that quote what the CLI actually prints.
Also: api.example.com becomes api.fluksio.com (the emails stay, since
bootstrap.py really defaults to admin@example.com and RFC 2606 reserves it);
the mqtt table gains the two settings it had drifted behind on and inject's
wording matches the engine; llms.txt lists the two connector pages that were
in the nav but not in it; and the two device/device_policy notes now agree.
Builds clean under `zensical build --strict`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015YrQnKV3bnQd4K342y8tKj
This commit is contained in:
+12
-14
@@ -1,7 +1,6 @@
|
||||
# Flows, nodes and messages
|
||||
|
||||
Three ideas hold the whole system up. They are worth twenty minutes, because
|
||||
almost everything else follows from them.
|
||||
Three ideas hold the system up. Almost everything else follows from them.
|
||||
|
||||
## A flow is a graph you did not draw
|
||||
|
||||
@@ -22,21 +21,20 @@ def process(temperature, setpoint=21.0):
|
||||
`decide` is downstream of `read` because it needs `temperature` and `read`
|
||||
produces it. Nobody drew a wire.
|
||||
|
||||
This is the one structural decision everything else rests on, so it is worth
|
||||
being explicit about the consequences:
|
||||
The consequences:
|
||||
|
||||
- **Fan-in is free.** Two nodes providing `temperature` are two producers of
|
||||
one message. The consumer does not change.
|
||||
- **A node runs when something it reads was published.** Not merely when
|
||||
something upstream of it ran: a node that produced nothing this time — held
|
||||
back by a rate limit, say — leaves what reads it on the value it already
|
||||
something upstream of it ran: a node that produced nothing this time (held
|
||||
back by a rate limit, say) leaves what reads it on the value it already
|
||||
has, and so does everything behind that.
|
||||
- **A wire cannot be wrong.** There is no wire. There is a name that either
|
||||
matches or does not, and the canvas tells you at edit time which it is.
|
||||
- **Layout is not a document.** The canvas computes the arrangement, so a flow
|
||||
has no stored positions to maintain, merge or fight over.
|
||||
- **Flows stay small.** A graph nobody can hand-arrange is one worth keeping
|
||||
small — which is the intent. Several atomic flows that name each other beat
|
||||
small, which is the intent. Several atomic flows that name each other beat
|
||||
one flow with sixty nodes in it.
|
||||
|
||||
### Message names are namespaced
|
||||
@@ -65,7 +63,7 @@ def process(reading, unit="C"):
|
||||
return {"shown": reading if unit == "C" else reading * 1.8 + 32}
|
||||
```
|
||||
|
||||
`reading` is a port. `unit` is a **setting** — a constant of this node's code,
|
||||
`reading` is a port. `unit` is a **setting**: a constant of this node's code,
|
||||
typed into its panel and stored with the flow. Both arrive as arguments, which
|
||||
is why a setting may not share a name with a port. See
|
||||
[Where a node's values come from](values.md).
|
||||
@@ -86,7 +84,7 @@ Types are not decoration. They are what lets the dashboard editor offer you
|
||||
only the messages a gauge can actually draw, and what lets the canvas refuse a
|
||||
binding before anything runs. See [Payload types](../reference/payload-types.md).
|
||||
|
||||
Everything on the wire is JSON. Bytes — a checkpoint, an image, a model —
|
||||
Everything on the wire is JSON. Bytes (a checkpoint, an image, a model)
|
||||
travel as an `artifact`: the bytes go to a content-addressed store and the
|
||||
message carries a small reference to them. The media types are that same
|
||||
reference, saying what kind of bytes are behind it.
|
||||
@@ -98,7 +96,7 @@ once at the end: a training loss, a progress fraction, a frame from a camera, a
|
||||
second of speech. A node publishes on one by being a generator and yielding, or
|
||||
by calling `fluksio.emit`.
|
||||
|
||||
Each value is delivered to the nodes reading it, in the order it was produced —
|
||||
Each value is delivered to the nodes reading it, in the order it was produced,
|
||||
so a recogniser slower than the microphone in front of it still sees every
|
||||
chunk rather than only the newest. What is in state remains the latest value,
|
||||
which is what everything else reads, and what a run keeps is the whole series.
|
||||
@@ -115,7 +113,7 @@ store, no handle to reach for.
|
||||
That is deliberate: a node with hidden state cannot run twice in parallel,
|
||||
cannot be replayed, and cannot be moved to another machine. Plenty of real
|
||||
automations do need to remember something, and there is a specific way to say
|
||||
so — see [Keeping state in a flow](state.md).
|
||||
so; see [Keeping state in a flow](state.md).
|
||||
|
||||
## Two shapes of flow
|
||||
|
||||
@@ -142,8 +140,8 @@ Every flow has a published version and, while you are working, a draft.
|
||||
- **Publishing** promotes the draft. The engine reloads and picks it up.
|
||||
- **Discarding** throws the draft away.
|
||||
|
||||
The store is a git repository — `flow.json` for the structure, `nodes/*.py` for
|
||||
the code — and each save is a commit. So a flow's history is readable with
|
||||
The store is a git repository (`flow.json` for the structure, `nodes/*.py` for
|
||||
the code) and each save is a commit. So a flow's history is readable with
|
||||
ordinary git tooling, and copying a flow between instances is copying a
|
||||
directory.
|
||||
|
||||
@@ -166,7 +164,7 @@ to:
|
||||
| `node_unhealthy` | the node loaded but is not working: a connector that cannot reach its device, or whose last publication failed |
|
||||
|
||||
A flow with any of these except the advisory one and `node_unhealthy` does not
|
||||
run — a node reporting itself down is a live condition, not a build error, so
|
||||
run. A node reporting itself down is a live condition, not a build error, so
|
||||
the rest of the flow keeps going and the issue clears by itself once the node
|
||||
reports well again. The health summary on Home counts them, so "why is nothing
|
||||
happening?" has an answer that does not involve reading logs.
|
||||
|
||||
Reference in New Issue
Block a user