Stage specification¶
A stage is specified by YAML in its docstring: description, arguments,
outputs, plus visual hints for the editor.
description: "Increment numeric value by delta"
icon: "+" # glyph, SVG link, data URI or inline <svg> markup
icon_mono: false # recolour the SVG to the node colour (monochrome sets)
color: "#ff8800" # card accent (defaults to the category colour)
icon accepts four forms:
| Value | What gets drawn |
|---|---|
"+", "👋" |
the glyph or emoji itself |
"/icons/globe.svg", "https://…/x.svg" |
an SVG by link |
"data:image/svg+xml;utf8,…" |
a data URI |
"<svg …>…</svg>" |
markup straight from the docstring |
icon_mono: true draws the SVG as a mask in the node colour, which suits
monochrome sets (lucide, feather, tabler) that paint via currentColor.
Without icon the editor draws a monogram of the stage name
(IncrementStage → IS); without color it picks a deterministic colour for
the category.
Four more things end up in get_specs(), and those are class attributes,
not docstring keys: category, timeout, allowed_events and
allowed_inputs (EventSpec / InputSpec with a payload_schema).
| In the docstring | On the class |
|---|---|
description, icon, icon_mono, color |
category |
arguments, outputs, reserve |
timeout |
allowed_events, allowed_inputs |
A category: or timeout: written in the docstring is parsed and then
dropped, and nothing warns. get_specs() reports the class attribute, so the
spec and the deadline the run enforces agree with each other and there is
nothing to notice: a stage that asks for timeout: 90 in its docstring alone
runs under the BaseStage default of 30 seconds, and the editor is told 30 as
well.
Prose has languages, identifiers do not¶
Everything in a spec is either an identifier or prose. A name, a type, a
category, a meter, a colour — identifiers, the same in every language, never
translated. The stage's description and the description of every argument,
output, event and input — prose, and prose has a language.
So any of those may be a {locale: text} mapping instead of a string:
description:
en: "Increment numeric value by delta"
ru: "Увеличивает число на delta"
arguments:
delta:
type: int
description:
en: "How much to add"
ru: "Насколько увеличить"
And get_specs() hands back every language it has, rather than choosing
one:
>>> IncrementStage.get_specs()["description"]
{'en': 'Increment numeric value by delta', 'ru': 'Увеличивает число на delta'}
That is the shape a client wants. An editor fetches the specs once and its reader picks a language afterwards — and picks again whenever they change their mind — so a spec that had already been narrowed to one language would have to be fetched again on every change of mind. The spec carries the choice; whoever draws it chooses.
Prose in one language stays a plain string, because a mapping of one entry is a choice that carries no information:
Naming a locale collapses the prose to it, for a caller that really is answering one reader rather than serving a client:
A mapping is the whole mechanism for a handful of stages. For a hundred, a
gettext catalog of your own is less repetitive, and the built-in stages use
one — both in Localization.
What it asks to reserve¶
reserve declares what a run of the stage may consume, in the
meters the host counts. Values are numbers or CEL over args,
the arguments as the stage will receive them:
It is declared rather than computed because it is read before the stage
runs: a host refuses a graph it cannot pay for without executing it, and an
editor can show the figure on the card. What was actually spent is reported
by the stage itself with self.charge(...), which is code, because the truth
is only known at the end.
This is what the specification above turns into on the canvas: the icon, the description, the arguments it reads and the outputs it writes.
