Subsystems
Any .weave file in a project can be run from inside another .weave, the
way a Simulink model reference works. There is no separate file type: a file
becomes callable by containing Subsystem Argument and Subsystem Return
nodes, and its interface (the names and order of its inputs and outputs) is
derived from those nodes.
Status. The engine, the three nodes, the canvas (file picker + dynamic ports), and Python export are all in place and covered by tests. Still to come: "extract selection into subsystem".
See
projects/demo_project/subsystem/for a worked example:circle_geometry.weave(a subsystem — one Argument, two Returns) called twice, with different arguments, fromsubsystem_circle_demo.weave. Every family of shipped demo now lives under a per-family subfolder ofprojects/demo_project/(polyweave/,simweave/,sklearn/,torch/,subsystem/, ...).
The three nodes (category Subsystem)
| Node | Where it goes | What it does |
|---|---|---|
Subsystem Argument (subsystem_input) | in the called file | An input. Fields: Name, Order, Type (a hint), Default. Emits the value the caller passed for its Name, otherwise its Default. |
Subsystem Return (subsystem_output) | in the called file | An output. Fields: Name, Order, Type. Passes its input through and previews it; the caller reads it back by Name. |
Run Subsystem (run_subgraph) | in the calling file | Runs another .weave of the project. Field: Subsystem file (project-relative path). One input port per Argument, one output port per Return. |
Because an Argument emits its Default when the file is run on its own, every subsystem stays runnable and testable standalone.
Names must be valid Python identifiers and unique among a file's Arguments
(and among its Returns). args_dict is reserved. Order decides port
order (lowest first, ties broken by node id).
How a call behaves
- The file on disk runs, not unsaved edits in an open tab.
- Each call has its own scope: its own dataset, no events on the parent's
canvas, the parent's Stop button applies, and its log lines arrive prefixed
[sub:<file>]. - If any node inside fails, the Run Subsystem node fails with the child's node id and message.
- An Argument left unwired uses its Default.
- The optional fixed args_dict input takes
{argument name: value}pairs and overrides wired arguments of the same name; an unknown name is an error (catches typos). Handy for parameter sweeps and calls inside a loop. - Recursion (
acallsbcallsa) is detected and reported; nesting is capped at 8 levels. - The subsystem file must be inside the project (
..escapes are rejected) and end in.weave. - An Argument can feed a
forloop in the called file: its value is resolved before the loop's iteration count is read.
Ports and the interface snapshot
Drawflow connections are by port index, so the calling node keeps a snapshot of the port names in its data:
{"weave_file": "sim/quarter_car.weave",
"dynamic_inputs": ["mass", "spring_k"],
"dynamic_outputs": ["displacement", "force"]}
Ports are args_dict, then dynamic_inputs in order; outputs are
dynamic_outputs in order. At run time values are matched by name, so a
called file that has since changed fails loudly (has no Return named 'x',
has no Argument named 'y') instead of silently shifting values between
ports. Without a dynamic_outputs snapshot the node follows the file's
current Return order.
Python export
A run_subgraph node's call is inlined the first time it's referenced:
PythonGenerator recursively parses the called .weave, exports it as a
nested def subsystem_<file>(arg=default, ...): ... return {"name": value, ...}, and turns the call site into node_N = subsystem_<file>(arg=<upstream expr>). Two calls to the same file — even from different subsystems —
share one definition; a subsystem that itself calls another is inlined the
same way, recursively. node_N["name"] reads a specific Return off the
result, matching the runtime's own MultiOutput port selection.
A wired args_dict port isn't representable in exported code (its value
isn't known until the script runs), so only that node's export is marked
unavailable — same as a missing or unresolvable subsystem file, or an actual
recursive call chain (a calls b calls a).
The exported script is still one file — a subsystem doesn't get its own
.py. See the "1 large file" discussion in the project's commit history if
that changes.
For node authors
ExecutionContext.run_subsystem(path, arguments)runs a child engine and returns{"interface": ..., "returns": {name: value}}.- A node with several output ports returns
backend.execution.multi_output.MultiOutput({0: a, 1: b}); each downstream connection picks the value for the output port it leaves from. Every other node keeps returning a single value. Declareregister_block(..., {"multi_output": True})so the exporter'sPythonGenerator._upstream_refknows to generatenode_N["name"]for a downstream reference (it reads the name from the node's owndynamic_outputsdata). register_block(..., {"dynamic_ports": True})marks a node whose extra input ports are named in its data asdynamic_inputs.register_block(..., {"codegen_custom": "..."})marks a node whose Python export is bespoke logic inPythonGeneratoritself (rather than the genericexpr/templatemechanism) —codegen_availablebecomes true without anexpr/templatepresent. SeePythonGenerator._generate_subsystem_callfor the one current user.