Visual editor
Fork a Loop and edit its body on a canvas — nodes, edges, a per-node inspector, an inline linter, and a Graph/DSL toggle over one canonical definition.
The visual editor is a fork-and-edit surface — there is no blank-canvas builder. You fork an existing read-only or workspace Loop and edit its body: add nodes, wire edges, configure each node in an inspector, and publish. It is one of three surfaces over the same canonical definition; the file and the API edit the exact same document.
The three regions
- Node palette (left), grouped by class:
- Action — the three reserved kinds
run-agent,run-loop,transform, a curated shortlist of common tools (including a Channel post shortcut that inserts a pre-filledcompozy__network_sendnode), and a searchable "Call tool…" picker over the whole tool registry. - Control —
fan-out,collect,branch,gate,sub-loop,wait,ask,route. - Source —
watch-source,file-import,input.
- Action — the three reserved kinds
- Canvas (center) — positioned node cards and edges on a dot grid. A selected node gets an accent ring; a node with an error gets a danger ring and a badge.
- Inspector (right) — the fields for the selected node.
Both rails are collapsible and ship collapsed, so a fresh editor opens full-bleed. Toggle them
from the toolbar or with [ and ] while the canvas has focus; the inspector also opens on its own
when you select a node, add one from the palette, or reveal a node from the linter dock. Rail state
and the dock's fold persist per user across reloads.
On the canvas: press a (or double-click empty space) for quick-add — pick a kind to place it
at the visible centre, or search an existing node id to reveal it. Drag a connection onto empty
canvas to get a picker that creates the node already wired from the source. Right-click a node
for duplicate, copy, paste, rename, and delete; on a read-only source loop those verbs are absent.
Marquee-drag or modifier-click to select several nodes for a bulk move or delete — deleting a node
removes its edges too. A selected edge carries a delete affordance at its midpoint, and an edge
leaving a route node shows the condition that chooses it.
The inspector renders from the schema
The inspector fields come from the canonical DSL types and the tool registry's own schemas —
never an editor-local model. A run-agent node shows agent, prompt, output_schema, and its
session envelope; a gate shows its supported criterion fields, verdict_policy, and
max_revisions; any tool-ID action renders a params form generated from that tool's input schema.
Runtime fields authored through the definition file or API round-trip without loss and are
validated by the daemon even when the current inspector does not expose an editor control for
them.
Four composite controls write shapes the DSL constrains, so each writes its whole value at once rather than leaving a half-shape the daemon would reject:
ask— prompt, context, the requiredexpectanswer schema, whether agents may answer, and an expiry. The run page renders its answer form from that sameexpectschema.route— the orderedroutes[]list (a condition plus a forward target each) and the requireddefault. Declaration order is the tiebreak the runtime evaluates in, so the editor preserves it exactly, and target pickers offer forward nodes only.strategyon a fan-out —wait_all,fail_fast,race, orbest_effortwith its threshold, plus themissing: acceptableacknowledgement, alongside thebind_as/index_asiteration names.reviewon an action node — the decision allowlist,when, prompt, responder policy,on_reject.route, and an expiry. Enabling writes the whole block; disabling clears it.
The linter is the daemon's
A dock along the bottom of the canvas shows structural invariants as pass/fail chips —
acyclicity, reachability, termination, fan-out bounds, references, routing,
and human requests — plus the current issue list. The editor surfaces the runtime's
validation; it does not invent its own. An issue such as a missing positive max_fan_out (code
fan_out_unbounded) links to the offending node, and Publish stays disabled until it clears.
Codes you will meet while authoring the newer kinds: route_default_missing,
route_target_invalid, route_mapping_invalid, strategy_threshold_invalid,
strategy_coverage_undeclared, iteration_name_conflict, ask_expect_required,
review_shape_invalid, review_respond_schema_required, and responder_policy_invalid. A code the
editor cannot attribute to a chip is still listed and still blocks Publish — the dock never claims
an invariant it cannot name.
Validate and publish
- Validate lints and compiles the draft without saving it (
POST /loops/:name/validate), returning deterministic per-node codes —unknown_reference,node_id_invalid,verdict_policy_requires_judge,fan_out_unbounded. - Publish persists the draft with
expected_version(PATCH /loops/:name). It is compare-and-swap: if the stored version moved since you opened the editor, the publish is rejected409with the current version, and the editor surfaces the conflict. Publish compiles and stores the resolved form the runtime consumes — never the author YAML.
This is the same authoring loop the CLI and API follow; the editor is a faithful render of the linter → 422 → publish gate.
Graph and DSL views
A Graph / DSL toggle switches between the canvas and the compozy.loop/v1 YAML on disk. The DSL
view is the same definition, so an issue highlighted on a node is highlighted on the corresponding
YAML line. In the graph view, start bindings show as a read-only chip strip pinned at the origin
with the note that they are edited in the DSL view — there is no graph-side start editing in this
version.
Node layout positions persist separately in a UI-annotation store keyed
(workspace_id, loop_name, node_id) (GET/PUT /loops/:name/annotations), so layout is never
mixed into the canonical definition. These endpoints require a live definition. Deleting a
workspace-authored Loop removes its config override and editor annotations while preserving run
history.
Authoring loop
The safe path to change a Loop — describe, validate, dry-run, then publish with a compare-and-swap version — with no LLM spend before you run.
Example: converse and decide
A documented Loop that posts a question into a channel, lets agents converse, harvests the agreed decision, and branches on it.