Composer
The Composer is the visual editor for scores. It’s where operators inspect the graph an automation runs, clone a template into a workspace copy, and author new scores from scratch.
The route is /composer for the index of every workspace score; /composer/$scoreId for a single score’s canvas. Edit mode is a ?edit=1 query param on the canvas URL.
Index page
Lists every score in the workspace as a card row: name, version, pipeline kind (outreach / content / seo), template badge for seeded starting points, count chips for deterministic + LLM + edge counts, “wired to” chips deep-linking to the orchestra that plays it.
Two affordances per row:
- Click anywhere on the card → open the score’s canvas in view mode.
- Clone button (top-right of the row) → creates a workspace copy of the score, opens it in edit mode. Use this on a template to start a customised version. The clone is fully self-contained — every LLM node’s referenced agent is also cloned, so editing the clone never affects the original.
A ”+ New score” button in the page header creates a blank score and drops you into edit mode. Most operators won’t start here — cloning a template is faster — but it’s the right entrypoint for genuinely-new automations.
Canvas: view mode
Default state when you open a score. Renders the graph with React Flow + dagre auto-layout — top-level nodes flow left-to-right, map_over body subgraphs drop down below their parent.
Three node-kind treatments:
- Deterministic — paper background, shows the skill + operation under the label.
- LLM — forest accent, shows the model + tool count.
- Control — brass accent, shows the subkind (
map_over).
Body-iteration members (nodes listed in some map_over’s body_node_ids) get a dashed left border and an “ITER” badge — visual hint that they run inside a loop without needing nested-container layout.
Three edge styles, with a corner legend:
- Flow (solid
ink-2) — sequential data flow between nodes. - Branch port (solid brass) — from a non-
successport (e.g.find_contactemittingexistsvsnot_found). - Iter item (dashed
ink-3) — amap_overbroadcasting the iteration item to a body node.
Hovering a node dims everything not on its upstream + downstream path — quick way to trace a node’s connections without clicking.
Click a node → side panel shows its full config (system prompt, args, schemas) read-only. Click an edge → side panel shows from/to/ports/transform.
Canvas: edit mode
Toggle from the canvas header (or open /composer/$scoreId?edit=1 directly).
The canvas gains a left-rail Skill Palette: every installed skill, expandable to its operations. Drag an operation onto the canvas → creates a deterministic node at the drop position. The palette header has ”+ LLM” and ”+ Control” buttons that drop stub nodes at the canvas center (operator fills config in the side panel).
Nodes become draggable — reposition by dragging. Positions persist on save.
Edge drawing: drag from a source-port handle (bottom of the node) to a target-port handle (top of another node). Self-loops and exact duplicates are rejected client-side; cycles and body-edge-constraint violations are rejected server-side at save time with a structured error.
Selection: click a node or edge to select. The side panel becomes editable:
- Deterministic node: label, args (rendered as a JSON-Schema-driven form against the operation’s
inputSchema), loop-body controls (skip_iter_when_field_set,run_node_when_field_set, etc.). - LLM node: label, agent picker (dropdown of every workspace agent, plus a ”+ New agent” option). Picked agent’s system prompt previews in a read-only block; full editing happens on the agent’s detail page.
- Control node: label, subkind (read-only — only
map_overin v1), structuredmap_overeditor (iter_field, item_field, body_node_ids multi-select, fail_fast toggle, collect_field).
Delete: select an element + press Backspace/Delete.
Reset Layout: button in the header strip — re-runs dagre over the live node + edge set, marks dirty. Operator escape hatch when an edited canvas drifts into a tangle.
Bezier toggle: small button in the edges legend — flips the edge type between smoothstep (right-angle) and bezier (curved). Per-canvas state, not persisted.
Save / Discard: header buttons. Save sends the dirty diff to PUT /api/composer/scores/$id/save — single transaction, version-bumps once regardless of how many ops were in the bundle. Discard refetches the server graph and resets local state (with a confirmation prompt if you have unsaved changes).
The dirty badge (“unsaved changes”) shows in the header when local state differs from server.
Save semantics
The Composer’s save is explicit — drag a node to a new position, the canvas is dirty; click Save to commit. Position drags don’t fire intermediate saves.
The save endpoint accepts a bundle: one transaction containing creates, updates, and deletes for nodes + edges + (optionally) per-LLM-node agent edits. Cycle detection and body-edge constraint checks run server-side before any DB write — if validation fails, nothing is applied.
After a successful save, the server returns the post-save graph; the canvas resets dirty and re-renders against the new graph. The score’s version increments by exactly one per save.
Last-write-wins for v1. Two operators editing the same score concurrently: whoever saves last replaces the other’s edits. Self-host operators on a single box don’t hit this; multi-tenant cloud will add If-Match semantics later.
Cloning a template
The seeded hero scores (B2B SaaS Outbound, Reply triage) ship with is_template: true. Click Clone on a template row → workspace copy is created with source_score_id pointing at the template, is_template: false. The clone is fully self-contained:
- All nodes are cloned with new IDs.
- Edges are cloned with the new node IDs remapped.
- LLM nodes’ referenced agents are also cloned (so editing the clone’s agents doesn’t affect the original’s). The new agents get a
-copyslug suffix. - Body-node references inside
map_over.config.body_node_idsare remapped.
Cloning is the recommended entrypoint for customising a hero score for your own ICP / sender / tone. The original template stays put for re-cloning later, and bootstrap re-seeds (template version bumps in releases) only touch templates, never workspace copies.
What’s NOT in the Composer (v1)
- Workspace skill authoring (Phase 6+) — you can’t write a new Python skill in-browser. Skills live in
skills/catalog/on disk. - Visual edge transform editor — transforms are edited as a JSON
Record<string,string>rename map. Visual field-mapping is a Phase 5.5+ polish. - Run replay / step-through debugging — see runs on
/orchestras/$id, not on the canvas. - Multi-cursor / collaborative editing — single-operator only in v1.
- Score templates marketplace — operators clone the seeded templates; sharing custom templates between workspaces is a v2+ feature.
- A/B variants of a score — fork via clone for now; first-class A/B comes later.
Related
- Scores — the conceptual model the Composer edits.
- Agents — workspace LLM-node configs (the ”+ New agent” picker links here).
- Orchestras — cron deployments that play scores. The Composer doesn’t manage these;
/orchestras/$id/settingsdoes. - Skills overview — operations the Skill Palette draws from.