Building workflows
Unified I/O & mapping
Every node declares what it produces and what it needs. Mapping is how a node’s input is bound to an upstream node’s output — by path, with types checked before you publish.
The output schema of a node#
A node’s output is not an opaque blob. Tools declare a schema, agents produce a structured result, and the engine records both the value and its shape at the commit boundary. That schema is what the next node can bind to.
{ "type": "object", "properties": { "rows": { "type": "array", "items": { "type": "object", "properties": { "account_id": { "type": "string" }, "renewal_date": { "type": "string", "format": "date" }, "mrr_gbp": { "type": "number" } } } }, "rowCount": { "type": "integer" } }}Binding an input to an upstream field#
In the inspector, each input shows the fields available from every upstream node. Picking one writes a binding: a path from a node id into that node’s output.
Bindings are by path, not by position. Reordering nodes on the canvas, renaming a node label, or inserting a step between two others never silently rewires a field — the path either still resolves or it does not, and a path that no longer resolves is a validation error rather than a null.
{ "accountId": { "from": "n1004", "path": "rows[0].account_id" }, "mrr": { "from": "n1004", "path": "rows[0].mrr_gbp", "coerce": "number" }, "tone": { "literal": "formal" }}Type coercion#
Bindings coerce only where coercion is lossless and unambiguous. Anything else is a validation error at publish time, because a silent coercion is a bug that surfaces three nodes later with no trace of where the value changed.
| Allowed | Rejected | |
|---|---|---|
number → string | Yes — formatted with the locale-independent representation | — |
string → number | Only if the whole string parses | "12 accounts" fails at publish |
string → date | ISO-8601 only | Ambiguous formats such as 03/04/2026 |
any → boolean | Only true/false and "true"/"false" | Truthiness. 0 and "" are not false here |
object → string | — | Rejected. Bind a field, or add an explicit serialise step |
What happens when a mapping breaks at runtime#
Publish-time validation catches missing and mistyped paths. Runtime can still surprise you: an upstream query returns zero rows, so rows[0].account_id has nothing to resolve against.
- 1
The node fails, it does not receive null
The binding cannot resolve, so the node never starts. Passing null downstream is how one empty result becomes four confusing failures. - 2
The error names the path
n1005.accountId: no value at n1004.rows[0].account_id (rows had length 0)— the path it looked for and what it found instead. - 3
Everything upstream stays committed
The query itself committed. A resume re-executes only the node whose binding failed. - 4
You fix it with a default or a guard
Give the input a default, or put a branch in front that handles the empty case explicitly.
Was this page helpful?