The editor¶
The editor is where you build an agent visually: drag node types onto a canvas, wire their ports together, and configure each one in a form — no code required. It is a three-column workspace over a single agent document, with a raw-JSON view for anything the forms don't expose. This page is a tour of every panel and interaction; the individual node types have their own reference pages.
If you have never built an agent, walk through your first agent first — it uses this editor end to end.
Opening the editor¶
The editor lives at /editor. You reach it from the Agents page (the sidebar's first entry, at /):
- New agent opens a blank graph.
- Clicking any agent card opens that agent's latest saved version for editing.
A blank graph is not empty. It arrives pre-seeded with an input node wired straight to an output node, named Untitled agent, version 0.1.0. That starter graph already passes validation and runs — it simply echoes whatever you send it — so you can save and run immediately, then build outward.
graph LR
IN["input"] --> OUT["output"]
When you open an existing agent, the sidebar collapses to icons to give the canvas room. Opening an agent that has no saved versions shows an error rather than a blank canvas.
The three columns¶
The workspace is three resizable columns:
| Column | Default width | What it holds |
|---|---|---|
| Palette (left) | 280 px | The node types you can add, grouped by category. |
| Canvas (center) | fills | The graph itself — nodes and the edges between them. |
| Inspector (right) | 450 px | Configuration for whatever you have selected. |
Drag the splitter between columns to resize (palette 150–420 px, inspector 260–620 px); double-click a splitter to reset it. Either side panel collapses to a thin labeled rail — selecting a node or edge auto-expands a collapsed inspector so you never lose the form.
The toolbar across the top carries the agent's id, name, and version fields, a visual / code toggle, the saved content hash (display only), a validation indicator, a status badge, Revert, and Save agent. Those last few are covered under Validation and Saving agents.
The palette¶
The palette lists every node type you can drop onto the canvas, under a header labeled Nodes with a search box ("Search nodes…") and category filter chips. The categories map onto a node's kind:
| Category chip | Kind | Contains |
|---|---|---|
| I/O & Human | boundary | human, input, output, subgraph |
| Compute & Tools | activity | imagine, llm, quota, ratelimit, speak, tool, transcribe |
| Control flow | orchestration | guardrail, loop, map, router, transform |
Groups are collapsible and show an item count. Types are sorted alphabetically within each category.
Where is the MCP node?
There is no separate MCP node in the palette. An MCP tool is one of the three kinds of the single tool node — you pick "MCP" in the inspector after dropping a tool node. A graph you import that already contains an mcp_tool node still renders fine. See MCP tools.
The canvas¶
The canvas is a pannable, zoomable surface. Kind colors are consistent everywhere: boundary nodes are green, activity blue, orchestration amber — the same tones appear on the node border, its kind dot, and the palette chips.
Adding nodes¶
- Drag a palette item onto the canvas to drop a node exactly where you release it.
- Click a palette item to add it at a cascading default position.
New nodes get generated ids like n_llm_1. A freshly dropped llm node is pre-seeded with one user message containing $in so it is wired up to receive its input.
Connecting ports¶
Nodes carry small handles you drag between to make an edge. Handles come in three channels, and you can only connect like to like:
| Channel | Handle look | Meaning |
|---|---|---|
| data | round, on the node sides (in on the left, out on the right) | Passes a value along the edge. Required in-ports are blue, optional gray; error out-ports red, normal green. |
| control | amber squares, top and bottom | Pure sequencing — "run after this", no value passed. Control edges animate on the canvas. |
| tool | violet squares (an llm's tools port on the bottom; a tool node's use port on top) |
A capability wire — "the model may call this tool". |
Trying to cross channels is rejected with a toast: cannot connect a <role> handle to a <role> handle — connect like-to-like. Feeding a second data edge into an in-port that already has one is also rejected — an in-port takes at most one data edge, because two would be an ambiguous input. (Control edges have no such limit.)
Wiring a tool node's violet use handle into an llm's tools port does more than draw a line: it registers that tool as a callable capability, and the tool node's id becomes the function name the model sees. See tools.
Moving, deleting, duplicating¶
- Left-drag on empty canvas draws a box (marquee) select; dragging any selected node moves the whole selection together.
- Middle-drag, right-drag, or hold Space and drag to pan; scroll to zoom.
- Right-click a node or edge for a context menu: Duplicate node (a fresh copy with a new id, its label suffixed " copy", offset slightly, with no edges carried over) and Delete node / Delete edge.
- Selecting something and pressing Del or Backspace deletes it.
Canvas controls¶
The bottom-left controls offer Tidy layout (a wand that auto-arranges the nodes by longest path — a layout-only change), plus Undo and Redo. A minimap shows the whole graph, and a ? button in the top-left reveals an interaction legend. A hand-written or imported graph with no saved positions is laid out automatically the first time it renders, so it is never a pile of overlapping boxes.
The inspector¶
What the inspector shows depends on your selection.
A node is selected¶
The header shows the node's kind badge, its type, and its id, with a per-node Wizard / Code toggle:
- Wizard is the form. A combined Label + icon row comes first (the label placeholder is the node id), then one field per config key. Field labels are humanized from the camelCase keys —
maxToolIterationsbecomes "max tool iterations". Required keys get a*. Enums render as dropdowns, nullable strings and numbers as inputs, and objects or arrays as a guarded JSON box that shows a parse error inline instead of silently dropping bad input. - Code is that one node's exact JSON in an editor — the escape hatch for anything the form doesn't surface. This is how you add extra
routerout-ports, pin acontentHash, or set advanced HTTP-tool fields. Invalid JSON shows ✗ invalid — not applied and is never committed. Renaming the node id in the JSON keeps it selected.
Several node types get purpose-built panels instead of the generic form — the llm model and message editors, the tool Kind picker, the guardrail Check picker, the pinned-body pickers for subgraph/loop/map, and the transcribe/speak parameter forms. Each is documented on that node's reference page.
Durable-only nodes
Adding a human, subgraph, loop, or map node shows a banner: these run only on the durable runtime. Save the agent, then use Run durably (which needs the server's durable mode) or deploy it behind a trigger. The plain interactive Run path cannot execute them. See durable runs.
The label and icon picker¶
The leading icon button in the label row opens an inline icon picker: a grid of suggested icons, a Search all icons… box over the full icon set (results capped, so keep typing to narrow), and Reset to default. Each node type has a sensible default icon, and any type the editor doesn't have a specific icon for falls back to a gear.
An icon is a display choice only. It is stored in the graph's layout block, which is never part of the content hash — changing an icon never creates a new agent version. The same is true of dragging nodes and zooming.
An edge is selected¶
The edge panel shows the edge id, a Delete edge button, and its source.handle → target.handle readout. A Channel toggle switches a data edge ("passes a value along the edge") to control ("pure sequencing — no value passed"). A tool edge's channel is read-only — to change it, delete and re-wire. There is also a Condition (router-driven, optional) field; routing in practice is done with the router node's select, and this field is reserved.
Nothing is selected¶
With an empty selection, the inspector shows the whole-graph panel:
- Model bindings — a read-only list of the logical models this agent declares (each as
binding · model). Bindings are declared for you when you pick an inference model on anllmnode; see models and engines. - Tools — a read-only registry derived from the tool nodes you have wired to an
llm. - Connections (tool / MCP auth) — an editable list of saved connections, plus a New connection form. A connection has a Name, a Kind (
http_authormcp_server), a non-secret Config JSON, and a write-only Secret field. The secret is encrypted on the server and never shown again — the connection stores only a reference, so rotating the secret never changes any agent's content hash. Connections are how HTTP tools and MCP servers authenticate.
Model parameters (the llm node)¶
When an llm node is selected, its inspector includes a Model parameters section that edits the parameters stored on that model binding — literal values shared by every llm node that uses the same binding, and part of the agent's hashed content. A note under the form spells this out: every llm node using this binding shares them. Empty fields send nothing (engine defaults apply).
The chat parameters, each with a ? tooltip explaining it in plain language:
| Parameter | Control | Range / values |
|---|---|---|
| Temperature | slider | 0–2 |
| Top-p | slider | 0–1 |
| Max tokens | number | — |
| Stop | text | comma-separated stop strings |
| Presence penalty | slider | −2 to 2 |
| Frequency penalty | slider | −2 to 2 |
| Seed | number | — |
| Reasoning | select | on / off |
| Reasoning effort | select | low / medium / high |
Two more fields appear only when the model advertises the capability: Tool choice (auto / none / required — needs tool-calling support) and JSON / structured output (text / json_object — needs structured-output support).
The reasoning toggle¶
The Reasoning select turns a model's hidden thinking phase on or off. Choosing "on" or "off" sends the model's chat_template_kwargs with {"enable_thinking": true} or {"enable_thinking": false}; leaving it unset uses the model's own default, and a model without a thinking switch simply ignores it. This is the same reasoning control offered in the Bench and referenced from chat — a model's thinking is always kept separate from its answer and out of the run output.
Presets¶
If you have saved parameter presets (from the Bench), a Load a preset… dropdown and a Load button appear. Loading a preset copies its values into the form — the preset's name never lands in the agent, and later edits to the preset do not follow the agent. The section is hidden when there are no presets for the modality.
The code view¶
The toolbar's visual / code toggle swaps the whole canvas for the full agent document as JSON — including the layout block — in an editor with syntax highlighting, line numbers, a fold gutter, Fold all / Unfold all / Format / Copy buttons, and find (Cmd+F / Ctrl+F). It autocompletes node types, kinds (boundary / activity / orchestration), channels (data / control / tool), bindings (mlx / vllm / llamacpp / openai-compatible), sources (hf / local-path / url), and common property names.
Two linters run: JSON syntax and the structural graph checks. The status reads ✓ applied or ✗ invalid JSON — not applied. Invalid JSON blocks both switching back to Visual and saving — fix it first. This full document view is the place to hand-edit anything the forms don't reach.
Validation¶
The editor validates continuously and mirrors the server's own checks, so what passes here is what the control plane will run. The toolbar indicator reads valid (green), N warnings (amber), or N issues (red), and toggles a floating Issues panel over the top-right of the canvas — hovering an issue flashes the offending node or edge, clicking it selects it.
Errors block Save. While any exist, the Save button is disabled with Fix N validation error(s) before saving. You will see errors like:
| Situation | Message you see |
|---|---|
| Two nodes share an id | duplicate node id |
| A type isn't a real node type | unknown node type |
A node's kind is wrong for its type |
type 'X' must have kind 'Y', got 'Z' |
| A required config field is empty | config '<key>' is required |
| A config value has the wrong shape | e.g. config/foo must be string |
An llm or guardrail names a model that isn't declared |
references undeclared model '<m>' (declare it in Model bindings) |
| A guardrail is half-configured | "a rule guardrail needs a rule"; "pick a judge model"; "add a judge prompt" |
A subgraph/loop/map isn't pinned to one body |
must pin EXACTLY ONE of 'version' or 'contentHash' |
| A loop has no bound | loop maxIterations must be a whole number ≥ 1 |
| An edge points at a handle that doesn't exist | '<n>' has no out-port '<h>' |
| An in-port is fed twice | in-port '<p>' on '<n>' is fed by >1 data edge (ambiguous) |
| A required in-port is unwired | required in-port '<p>' is not connected |
| A tool node is wired as both a capability and a step | "a tool node is either a capability (wired to an llm) or a step, not both" |
| The graph forms a cycle | "graph has a cycle (not allowed)" |
input nodes and tool nodes used purely as model capabilities are exempt from the "required in-port not connected" rule. Tool (capability) edges are excluded from cycle detection.
Warnings do not block Save — they flag things worth a second look:
- graph has no nodes;
- a
humannode whoseonTimeoutisdefaultbut with no default value set (a timeout would flownull); - the graph contains durable-only node types — run it with Run durably;
- more than one
outputnode — make sure at most one executes per run (two live outputs fail the run).
The code view runs the same structural graph checks as the visual canvas, so the same warnings and errors surface there too.
Running and testing¶
There is no run button inside the editor. Save the agent first, then run it from the Agents page: every agent card has a Run button that opens a bench modal. It is a split button — Run streams the agent interactively, and the caret menu offers Run durably (checkpointed; needs the server's durable mode). A durable-only agent shows a single Run durably button, because its nodes can't execute on the interactive path.
graph LR
Build["Build on canvas"] --> Valid{"Valid?"}
Valid -->|no| Build
Valid -->|yes| Save["Save agent"]
Save --> Run["Run from the Agents page"]
For the full save flow — ids, versions, and conflicts — see saving agents. For run output, statuses, and durable execution, see running agents and durable runs.
Keyboard and mouse reference¶
| Input | Action |
|---|---|
| Cmd+S / Ctrl+S | Save (only when validation passes) |
| Cmd+Z / Ctrl+Z | Undo (history of 100 steps, including drags) |
| Cmd+Shift+Z / Ctrl+Y | Redo |
| Esc | Deselect |
| Del / Backspace | Delete the selection |
| Left-drag empty canvas | Box (marquee) select |
| Middle / right / Space-drag | Pan |
| Scroll | Zoom |
| Right-click a node or edge | Context menu (duplicate / delete) |
Undo restores node positions too, so a mistaken drag is one keystroke away. Text fields keep their native undo while you are typing in them.
Leaving with unsaved work
Navigating away inside the app with unsaved changes shows a Leave with unsaved changes? prompt; closing or reloading the tab triggers the browser's own warning. A pure layout change — dragging a node, changing an icon — does not count as "modified", because it never changes the agent's content hash.
Related pages¶
- Referencing inputs — the
$intoken language you use in message and tool fields - Saving agents — ids, immutable versions, and running what you save
- Node reference — every node type, its ports, and its configuration
- Nodes, ports and edges — the underlying graph model
- Models and engines — logical model ids and bindings