Skip to content

Flows and nodes

A flow is an execution graph. It is made of typed nodes connected by outcome-routed edges. A Flow Node is the unit of work on the canvas. Every node has a single node type drawn from one shared vocabulary. The catalog, the DSL parser and serializer, the executor, the validator, and the canvas all agree on that vocabulary, so there is no separate “DSL kind” concept.

Node typePurposeExecutes as
actionTool-based work via a registered adapter (shell, filesystem, or a CLI tool). The adapter is chosen by data.adapter, its arm by data.actionId.the adapter
aiGenerative model invocation. A provider field selects the backend: local runs an on-device LLM (zero egress); a cloud provider invokes a frontier model, gated by settings and an API key.the executor’s AI path
agenticDesign-time flow generator: from a natural-language request it proposes a whole flow for review and apply.nothing. The executor skips it, and Run routes it to the review flow
utilityBuilt-in primitives that need no external adapter: sleep, log, set-variable, download, merge (fan-in), cron (schedule marker).the utility adapter
serviceAPI-based integration: connect to an external service through its API. The concrete service is catalog data.the generic service adapter

The canonical type, mirrored between the Rust core and the TypeScript contracts:

export type FlowNodeType =
| "action" | "ai" | "agentic" | "utility" | "service";
export interface FlowNode<TData = Record<string, unknown>> {
id: string; // unique within the flow
type: FlowNodeType;
label: string; // display name on the card
data: TData; // defaults from the catalog entry, then user edits
}

The wire shape keeps type open-ended so a loaded graph that contains a canvas preset or a not-yet-installed type still round-trips. The canvas renders an “unsupported” stub rather than dropping the node.

Flow Studio is catalog-driven. Every non-built-in node type on the canvas is one JSON catalog entry that is authored as data, not code. An entry carries everything needed to drop the node onto a canvas:

  • schema is a JSON Schema subset that drives the generic inspector’s form and both the frontend and backend validators.
  • defaults are the values stamped onto node.data when a fresh node is dropped from the palette.
  • commandTree is embedded reference data, for example a parsed CLI command catalog, that the inspector reads to render cascading command pickers. There is no per-kind code path.
  • settings are the declared global-settings dependencies. The inspector surfaces a “Requires” hint when something is missing.
  • lowering is the adapter and actionId used when serializing to DSL.

Adding a new catalog-driven node type is purely a JSON authoring exercise. You author the entry and install it from the Node Hub, and it appears on the palette with the right inspector form. There is no per-type React component to write. See the Node Hub.

Built-in carve-out: the ai and agentic types ship as first-class built-ins. They are merged into the installed-nodes store so the palette, factory, and validator treat them like any other type, but they never appear in the Node Hub and cannot be uninstalled.

Service nodes connect to external services through their APIs. The code is vendor-neutral. One generic service adapter executes the call, and everything service-specific lives in the catalog entry’s serviceIntegration descriptor. That includes the vendor name, base URL, auth scheme, and operations.

"serviceIntegration": {
"baseUrl": "https://api.example/v1",
"auth": { "scheme": "oauth2" }, // oauth2 | bearer | basic | api_key
"operations": [
{ "id": "list-items", "label": "List items", "method": "GET",
"path": "/items/{itemId}", "query": ["limit"] }
]
}

At run time the adapter resolves the node’s catalog slug to its integration, fills {param} holes from node.data, attaches the connection credential from the OS keyring, and sends the request. OAuth2 connections run authorization-code with refresh through the system browser with loopback capture, so there is no manual code paste. Service egress crosses the isolation boundary, so it is audited and gated by connection presence.

A Flow Node is extended along four axes. Each one is data or registration, with no per-type code:

  1. New catalog types. Author a JSON entry.
  2. New adapters. This is a contained, code-level change, and action nodes target the new adapter by name.
  3. New service integrations. Add a service entry with a serviceIntegration descriptor.
  4. New AI providers. Register a cloud provider, and the unified ai node picks it via provider.

The two code-level seams are documented in Extension APIs.