The node edit panel
Selecting a node on the canvas opens a tabbed edit panel — Settings, Input, Output, State. Settings is the per-kind configuration form and its Update-node action; a fan-in or loop node’s Settings also carry the barrier’s wait knobs (wait mode and the timeout give-up pair) and, where the flow binds states, the opt-in state-refresh toggle. Input and Output are read-only: they render the node’s declared input and output schemas — with the node’s outcomes surfaced on Output — so a node’s contract is readable in build mode, and each shows an empty state when nothing is declared. A scheduled node’s Settings also carry an optional run condition — a jq predicate that gates whether the node runs at all. A false condition skips the node: it does not execute, its output is null, its status reads skipped in Run mode, and the flow continues past it (the gate stops the one node, not the path). The field is absent on a node that runs only as an on-error handler, since such a node is dispatched directly and never scheduled. When an agent node carries a structured-output schema, the Output tab renders that schema live and its label reads Output (structured). Editing that schema to remove a field surfaces a non-blocking honesty note on the agent’s Settings: static analysis scans the flow for downstream jq that still reads the removed path off this agent (.upstream_outputs.<agent>.<path>, including the flow-level on-error handler) and
names the references that would silently start resolving to null — it never blocks the
save (references built dynamically are, honestly, not detected). The State tab is the
node’s state binding — the shared binding editor, one per node — and
its tab label carries a live count of the states the node binds.
Schema-aware run results
When you run a flow in-page, the run panel and each node’s run popup render results schema-aware. Where a node declared its output shape — an agent’s structured-output schema, or a preset’s (or the whole flow’s) output schema — the actual result renders as a labeled, typed display keyed off that schema: the schema’s titles as field labels, its descriptions as captions, enum values as chips, nested objects as sections, and a declared array as a list of its typed items. The view stays honest about the data: value keys the schema does not declare are never hidden — they render as an appended raw-JSON remainder — a declared field absent from the value shows a muted dash, and a value that does not match the schema’s shape at the top level falls back to the plain JSON tree with a small note rather than a crash. A per-instance Raw toggle flips the whole view to the plain tree, so the underlying data is never more than one click away, and a flow (or node) that declares no output shape keeps the plain results tree throughout.The LLM router card
The LLM router card carries its own edit surface: the kinds it classifies into, optional turn-level output fields that describe the whole classified turn (each a top-level output beside the kinds), the author’s system prompt, a routing mode — every match (the default, firing every matching route) or first match (the first matching route wins) — and a routes table listing every wired route with its flavor, condition, guard, and target. A route’s target is the edge drawn from its chip on the card, so the table shows it rather than picking it. Under first match the table also orders the rows — the row number is the evaluation priority — and a row reorders by dragging its grip or stepping its move buttons; the table’s advisory notes flag a row an earlier one already covers, without ever blocking the save. The card also carries one Dispatch control, set once for the whole card — Once (every matched chip continues the flow along its line) or Loop (each chip’s line starts a body that runs once per classified item, sequentially or in parallel with an optional max-concurrency cap, or repeatedly while an authored jq condition holds under a max-runs safety cap). Under Loop the card grows a result chip through which the collected output of every chip leaves. The chips look and connect the same in both modes; the modes are documented on the router page.The updates half of the binding editor
A node’s state binding applies updates to a bound state after the node runs, edited in the Updates list of the shared binding editor. Each update writes one of two ways:- a template jq the state’s attached templates declare (
purpose: "update"), picked from a select that lists each update program asname — description; the selected program’s declared write paths show beside it as awrites:badge. The run applies its returned op batch — keyed list ops and appends — under the attachment path. Because a named update jq never sees a tool’s field names, an adapter form maps the node’s{output, input}to the update jq’s declared input — one row per declared key, each picking a field, a literal, or a jq, with a write-jq escape hatch for the whole adapter. - a custom jq over
{record, output, input}returning the op batch itself, for a write the templates do not name.
Presets
The palette’s Presets section manages a preset as a node: an edit control opens its flow editor, a + New preset action starts a fresh one, and a delete control raises a guarded confirm that states the blast radius — the node leaves the palette and any flow already embedding it keeps a dangling reference. When a node edit removes an outcome value that wired callers route on, the make-a-node dialog shows an informational warning naming the removed values and how many flows use the node (their routes would go dead) — it never blocks the save. This inform-don’t-block convention is the editor’s house style for changes whose impact lands on callers. The one thing the make-a-node dialog does block on is a genuinely invalid input-schema edit. The make-a-node dialog’s input-schema editor nulls its buffer while an edit is unparseable, and saving then would silently erase the schema — the right outcome for a deliberate clear, the wrong one for a typo. So Save is disabled precisely while the input-schema buffer is invalid (a bad shape, not an empty one), with an inline “fix the input schema before saving” note; a cleared buffer reports valid and saves a null schema as intended. This is a validity gate, distinct from the non-blocking caller-impact warnings above.Message and expression slots
A message slot on the agent form is edited as a Plain / Template tabbed control: Plain is a raw message textarea, and Template picks a stored template with a deep link to the file and a collapsed preview of its first lines, editing its kwargs through a schema-assisted form over the authoritative JSON. A jq expression slot — on the conditional, branch, runnable, on-error, and agent forms — is instead an Inline / Template control: the Inline tab is the visual jq editor’s field (below), and Template is the same picker. In both, the inline value and the template are mutually exclusive — the wire carries one, and switching between them clears the mode being left after a confirm when it holds data. A legacy node that carries both an inline message and a template opens on the Template tab with a rescue note and a one-click “clear inline text” action, so the exactly-one-of rule is fixable in place. A node with a return result may carry a return expression (jq) that shapes the value it contributes to the flow result, and a conditional’s branch may be marked pass-through to inject the conditional’s single upstream output verbatim — which excludes a post-route expression and a return. Settings the form does not model — kwargs an API-authored or newer agent carries — are surfaced read-only so nothing an agent persists is fully invisible, and are round-tripped untouched. A jq field that can reach a bound state — a binding field, a node expression — offers autocomplete from the template jq of the templates attached to that state: eachpurpose: "input" program by name, inserting its tjq_<name>({…})
call handle (tjq_<template>__<name>({…}) when a name is shared across the state’s
attachments).
The visual jq editor
The Inline tab’s field is the visual jq editor: a jq text control with a Visual editor button that opens the canvas in a dialog, where the expression is drawn as a small, editable node graph and written back as jq. The dialog wraps a Test panel (run the jq against a sample seeded to the field’s input shape, plus a server Validate where the engine exposes one), a live readout of the jq the graph currently serialises to, an error summary that jumps to each problem, and a Legend of the node kinds and wire notations. The node vocabulary and its semantic hues — Input, Value, Operator, Condition, Try/Catch, Call Function, Define Function, Comment — are documented with the feature. The editor draws a graph only when it can prove that reading it back gives exactly your expression: a WASM oracle round-trips the graph and adopts it only when the two behave identically. Otherwise the fallback is non-destructive — an expression the editor cannot draw, or one whose graph would read back differently, keeps your text verbatim and shows it as text with a neutral “not shown here” notice; nothing is altered unless you explicitly Start empty. This keeps two questions apart — is the jq valid and can the editor draw it — so only jq that genuinely fails to compile raises the loud “invalid expression” alert, and valid jq the editor cannot represent is never mislabelled as broken. On opening the dialog, a faithful expression is drawn on the canvas; an unfaithful one shows the fallback, and the editable canvas is then reached only through Start empty.State bindings
Flows bind to the platform state store rather than a store of their own, so declaring states, attaching templates, and browsing records all live on the Studio’s States screen. What the flow editor owns is the binding: how each node reads and writes those states. Every node’s edit panel carries a State tab, and its binding is the same editor the platform’s door screens use — shipped from the Studio SDK so a door and a node bind a state the same way. The editor attaches one or more states to the node. Each state card picks the state and one or more templates (a template not yet attached is attached on save), then a Subject expression (a full subject object or a bare key) and an optional Scope predicate that gates the state for the run — the same on every door and node. Below the card, the Inputs list injects a value into the node’s input before it runs — each row apurpose: "input" template jq (or a custom jq
over {record, input}) and the input field it lands in — and the Updates list applies
a write after the node runs (the updates half
above). Every jq field autocompletes from the attached templates’ template jq.
Where the node’s tool is a registered preset that carries its own door binding, the editor
shows an advisory on any state both bind — Overrides the preset’s subject — with the
preset’s own subject shown as the read-only default beside it; the node’s binding wins.
Every control rebuilds from the stored binding: an update’s adapter form recovers each row
from the stored adapter jq, and a state or template the binding names but the server no
longer serves shows a Template not attached. error with the raw jq, never a blank
control. A refused save renders the engine’s message verbatim.
Upload
The flows toolbar carries an Upload action for a flow or node document with akind,
reporting the uploaded kind and name in a status line and prompting when a file carries
no kind. State and state-template documents are uploaded from the
States screen instead.


The flows toolbar Upload action with its result line.

