Composites and sub-workflows
When a graph stops fitting on the screen, a piece of it can be folded into a single card — a composite. Inside it lies a full subgraph with its own nodes and edges; from the outside it is an ordinary node with the ports you assigned it. Composites are available on the free plan, like every other node.
Folding one, and looking inside
The composite is not in the palette — you cannot drag it in, it is only ever created out of a piece of graph you have already drawn.
- Select two or more nodes. A bar appears above the canvas with the number of selected nodes
and a Merge button (that label is not localized);
Ctrl+Gdoes the same thing. - The selected nodes disappear from the canvas and one composite card takes their place.
- Double-clicking it opens the body: the canvas switches to the subgraph and a
Root › Compositetrail appears at the top left. Clicking a link in the trail goes back up. - The reverse is the Ungroup button above a selected composite, or
Ctrl+Shift+G: the body’s nodes return to the canvas and their edges are restored.
Entry and Exit cannot be folded in — the attempt simply does nothing. That is deliberate: a workflow has exactly one entry point, and hiding it inside would produce a graph with no entry on the outside.
Interface ports
Inside the body, the boundary is marked by two service nodes: In and Out. The body must
contain exactly one of each — otherwise validation reports composite.in_count or
composite.out_count, and a composite with no body at all reports composite.no_subgraph.
A composite’s ports are not derived from the body automatically: they are a separate list you edit in the node’s settings, giving each port a label, a direction and a wire type. Folding fills that list in for you — from the edges that crossed the selection boundary: every edge coming in from outside becomes an input port, every edge leaving becomes an output, and the wire type is inherited from the original edge. That is why a folded piece of graph stays wired exactly as it was.
The body is validated as a fragment, not as a workflow of its own: the “exactly one Entry” rule does not apply to it, and In serves as its entry point. Everything else — node types, wire compatibility, cycles — is checked by the same rules, and problems found inside surface on the outside with the composite’s name in front of the line.
Name isolation
The body executes in its own namespace of node outputs. In practice that means:
- from the outside,
{{ nodes.<a body node>.output }}does not work — the engine does not see the inner nodes; what comes out is only what the body placed on the output interface ports; - from the inside, the outer graph’s nodes are equally invisible: values arrive only through In;
- identical identifiers are harmless:
template_1inside a composite andtemplate_1outside are different nodes. In the run report the inner ones are shown with a prefix:composite_1/template_1.
What is separated is specifically the node outputs. Session variables (the Set Variable node) are shared: what the body wrote is readable outside and the other way round — otherwise a composite could not accumulate state. The same principle is on the state and variables page.
Nesting, and its limit
A composite inside a composite is allowed: the body is an ordinary subgraph and may hold another composite. The depth is capped — 5 levels by default, and the value comes from the plan. The same counter counts WF tool calls and an agent acting as another agent’s tool: it is one shared recursion limit, not three separate ones. Exceeding it stops the node with a budget error — see timeouts and budgets.
Iteration shells (While Loop, For each, Map, Filter, WF tool), on the other
hand, cannot be nested inside one another: the validator answers
subgraph.nested_iteration_shell. The details and the ways around it are on the
loops and iteration page.
A composite has no timeout of its own
The per-node time ceiling deliberately does not apply to a composite, to WF tool, or to an agent. Their own time is the sum of the times of the nodes they start, and each of those is already capped. A shared ceiling would mean the outer node fails on a timeout while the inner ones are honestly working, and the failure would arrive somewhere other than its cause. What bounds such a run is the whole run’s time ceiling and the ceilings of the individual body nodes.
WF tool: a subgraph as an agent’s tool
WF tool is the same “shell plus body” construction, except it is triggered by the model’s
decision rather than by execution order. The node is attached to an agent’s extensions port
over a link_extension wire, and you give it a name, a description and a JSON schema for its
arguments; when the model decides to call the tool, the arguments arrive on the Data output
(that starts the body), and whatever comes back into the Result input is handed to the model
as the call’s result.
- Execute + Data
- Extension
The node has no execution input: as a step of the graph it never runs, and its body executes as many times as the model calls the tool — possibly zero. The name and the description are all the model has to go on when choosing, so “do the useful thing” is not what you call a tool. The fields and the constraints are on the WF tool node page; the whole picture on tools is in the agent’s tools.
A composite can also be attached to an agent over a link_extension wire, but that means
something different: it does not become a tool itself — it hands the agent the tools and the
memory that live inside its body.
Reusing across workflows
There is no node in Flow that calls another saved workflow. Composites and WF tool reuse a piece of graph inside one workflow. Between workflows there are three routes today:
- copy the nodes — select,
Ctrl+C,Ctrl+Vin the other workflow; the composite’s body travels with its card, and a pasted Entry is attached to the existing one, so a second one never appears. The clipboard lives in the open tab: reloading the page loses it; - start from a template — the ready-made assemblies under templates;
- call it over HTTP — deploy the second workflow behind a webhook and reach it with the HTTP Request node. The webhook is a paid trigger; the free plan only allows the public chat and the widget, see plan limits.