Scores
A score is the graph that defines what an automation does. It is the central object in Maestro — every running automation has a score backing it, and “build your own automation” means “compose a new score.”
A score is a directed graph of typed nodes connected by typed edges. The Maestro runtime walks the graph topologically: each node receives merged input from its upstream edges, executes (deterministically or via an LLM call), and writes its output for downstream nodes to read. Branching, looping, and short-circuits are first-class.
This page explains the conceptual model. The Composer page covers the editor UI. The B2B SaaS Outbound page walks through the v1 hero score in detail.
Node kinds
Every node belongs to exactly one of three kinds:
Deterministic
Invokes a single skill operation — Apollo’s find_leads, Pipeline’s add_contact, Gmail’s send_email. The runtime hands the merged upstream input to the skill operation as JSON, the operation runs, the result becomes the node’s output. No LLM is involved.
Deterministic nodes are the bulk of any real score. They’re cheap (no token cost), fast (no model latency), and predictable (the same input always produces the same output).
LLM
A scoped Claude session that reads the merged upstream input and produces a structured JSON response. Each LLM node references a workspace agent — a reusable record holding the system prompt, the model id (claude-sonnet-4-6, claude-haiku-4-5-...), and the optional list of skills the agent can call as tools during its reasoning.
The model only sees what flows in via edges. It cannot read across the score, cannot see upstream-of-upstream node outputs unless an edge explicitly carries them, and cannot leak credentials (the SDK resolves secrets the model never sees).
LLM nodes are where the cost lives. A run’s USD total is dominated by tokens these nodes burn.
Control
Interpreted by the orchestrator itself, not dispatched to a skill or a model. v1 ships one subkind:
map_over— iterate an array on the input, run a body subgraph once per element. The body subgraph is a list of node IDs declared on the map_over’sconfig.body_node_ids. Per-iteration outputs collect into a configurable output field.
branch, gate, and parallel subkinds are reserved for future versions but not implemented in v1.
Edges and ports
An edge connects a source node’s output port to a target node’s input port.
By default, every node has a single source port named success and a single target port named default. Branching nodes declare additional ports — find_contact uses exists and not_found so downstream nodes can fork on whether the contact was already in the pipeline. Edge fromPort selects which branch to follow.
Each edge optionally carries a transform — a Record<string, string> rename map applied after the default identity-by-name input resolution. The orchestrator resolves a node’s input by merging every upstream edge’s output (renamed if the edge has a transform). When two upstream nodes both produce a field of the same name, the later one wins.
Iteration items broadcast from a map_over to its body nodes are the “iter item” edges in the Composer’s edge legend (rendered dashed) — semantically distinct from sequential flow edges (solid) and branch port edges (brass-coloured).
Versioning and runs
Every score carries a version integer that increments on every save. Each run snapshots the version it executed against (score_runs.score_version), so historical runs reconstruct correctly even after a score has been edited many times.
Two operators saving the same score racing each other is last-write-wins in v1. Self-host installs are single-operator so this doesn’t matter; multi-tenant cloud will add If-Match semantics later.
A score with is_template: true is a starting point for cloning. The hero scores ship as templates. Cloning produces a workspace copy (is_template: false, source_score_id pointing at the template) — the clone is fully self-contained and bootstrap re-seeds never touch it.
Scores vs orchestras
A score is a graph. An orchestra (orchestras.md) is the cron-attached deployment of a score in a workspace — the row that holds the schedule, the enabled toggle, the secret bindings. Orchestras play scores.
An orchestra points at exactly one score via score_id. Multiple orchestras can point at the same score (think: same graph, different schedules or different workspaces in the v2 multi-tenant world). Today the lab box has one orchestra per score, but the architecture doesn’t constrain this.
The split matters because it lets you edit the score graph (in the Composer) without touching schedule or secrets, and edit the orchestra’s metadata (on /orchestras/$id/settings) without touching the graph.
Agents
Every LLM node references an agent. Agents are first-class workspace entities — reusable system-prompt + model + allowed-tools configs. An agent might be referenced by one LLM node in one score, or by several LLM nodes across several scores.
Editing an agent’s system prompt updates every score that references it on the next run. The agent’s detail page shows which scores use it, so the operator can see the blast radius before saving.
LLM nodes can also be created with their agent inline (via the Composer’s ”+ New agent” affordance in the LLM-node side panel) — the agent gets a workspace-level row but is initially used by only that one node, and the Agents catalog filters between “shared” and “single-use.”
Score-runtime semantics in 60 seconds
When an orchestra fires:
- The orchestrator loads the score’s nodes + edges from the DB once at run start. Mid-run edits don’t affect the in-flight run.
- Compute the topological order. Mark all source nodes as ready.
- For each ready node:
- Resolve input from upstream edges (apply transforms, merge by name).
- Validate input against the node’s
inputSchemaif declared. - Dispatch:
- deterministic →
Registry.dispatch(skill, op, input) - llm → scoped Claude session with system prompt + filtered tool list
- control:map_over → iterate the input array, run the body subgraph per element, collect outputs
- deterministic →
- Capture the result in
score_run_nodes.output_json. - Mark downstream nodes ready when all their dependencies are done.
- When the worklist empties, finalize: compute
cost_centsfromscore_run_nodestoken rows ×model_pricing, writeruns.outputsummary, fire the SSE notify event.
The whole walk is a single async loop in apps/runtime/src/maestro_runtime/score_runtime.py. Read it if you want the ground truth.
Related
- Composer — the editor UI for authoring + editing scores.
- Agents — workspace LLM-node configs that LLM nodes reference.
- Orchestras — cron-attached deployments of scores. Orchestras play scores.
- Skills and tools — what deterministic nodes call.
- B2B SaaS Outbound — the v1 hero score, end-to-end.