Typed connections
Every edge is validated against the canonical
type catalog at draw time. You cannot wire a
Force output into a Length input — the editor refuses it live.
A workflow is a directed acyclic graph of functions, each backed by a
specific, pinned FunctionVersion. The output of one node feeds the input of
another through typed ports. Runtime values the user supplies are materialised
as input nodes — typed nodes that sit in the graph like any other.
Workflows solve a recurring engineering need: the result that matters rarely comes out of a single function. A typical sizing problem reads like “compute the wind load on a structure, feed it into a bolt sizing, then check the resulting shear stress” — three functions chained end-to-end.
Typed connections
Every edge is validated against the canonical
type catalog at draw time. You cannot wire a
Force output into a Length input — the editor refuses it live.
Computed visibility
A workflow’s visibility is not declared — it is derived from the visibility of every referenced package. Public everywhere ⇒ public. Any private node ⇒ private.
Pinned versions
Each function node freezes (package_slug, function_name, package_version)
when added. Updating the underlying package does not change the workflow —
reproducibility is first-class (FRO-pkg-05 applied to graphs).
Fail-fast
If a node fails, every downstream node is cancelled. Independent branches that were already running finish their current execution, but no new job is submitted on them.
╭──────────╮ │ I_bolt │ (input: {d, p, As, Re_min}) ╰────┬─────╯ ┌──────────┐ │ ┌───┤ Bolt ├── margin ─┐ ├──────────────────────┤ └──────────┘ │ ┌───────────┐ │ │ ├────┤ Validator │─ verdict ╭────┴─────╮ │ ┌──────────┐ │ └───────────┘ │ I_loads │ └───┤ Stress ├── stress ─┘ ╰──────────┘ └──────────┘Three building blocks:
node_key (stable
identifier) and a pinned function_version_id.label, a type_expr
(any canonical type, including structs like {d: Length, p: Length}),
whether the value is required at submission. A single input node can feed
many function ports — that’s how you share the same parameter across
two branches of the graph.Duplicating a compute node does not duplicate its inputs: the duplicate can either reuse the same input node (same value in both branches) or be wired to a new one (independent values). The graph itself is the source of truth for sharing — there is no hidden rule on top of the topology.
Input nodes can emit any type from the catalog:
Force, Length, Moment…)list[Force]) or dict (dict[str, Length]){d: Length, p: Length, As: Area, Re_min: Stress, quality_class: String}The run form generates one field group per input node and renders struct shapes recursively.
| Feature | v1 | v1.5 | v2 |
|---|---|---|---|
| DAG (parallel branches) | ✅ | ||
| Visual editor (React Flow) | ✅ | ||
| Explicit typed input nodes | ✅ | ||
| Typed connections with sous-typage | ✅ | ||
| Fusion of same-package steps (one cold start per chain) | ✅ (plan) | ✅ (worker) | |
| Warm pool | ✅ (FRO-wkf-12) | ||
| Import / export YAML | ✅ (FRO-wkf-13) | ||
| Logic nodes (if / loop) | ✅ (FRO-wkf-15) | ||
| Meta-workflows | ✅ (FRO-wkf-14) |
Type system
Canonical catalog, sous-typage rules, composite types.
Visual editor
Palette, drag-and-drop, live edge validation, properties panel.
Running a workflow
Run submission, tick polling, terminal outputs, failure handling.
API reference
Endpoints for CRUD, versioning, runs, and live polling via /tick.