Maestro

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-success port (e.g. find_contact emitting exists vs not_found).
  • Iter item (dashed ink-3) — a map_over broadcasting 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_over in v1), structured map_over editor (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 -copy slug suffix.
  • Body-node references inside map_over.config.body_node_ids are 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.
  • 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/settings does.
  • Skills overview — operations the Skill Palette draws from.