Maestro

One command becomes a plan you approve, workers in their own git worktrees, and a review of each diff by a different vendor’s CLI. The run is done only when every gate is green.

You give Maestro a single instruction. A planning model decomposes it into worker tasks, each owning a disjoint set of files. You approve or edit that plan. Each approved task gets its own git worktree and its own pane, and each finished task’s diff is reviewed by a different agent CLI before anything merges. Nothing is taken on a model’s word: “done” is a predicate over worker status, review verdicts and your own verification checks.

Before a run will start

Maestro refuses early and says why. Each of these is checked before the run row is created.

RequirementWhat happens without it
An active Pro subscriptionRefused: “Maestro orchestration is a Pro feature.”
An open workspaceRefused: “Open a workspace first.”
That workspace is a git repositoryRefused: “The workspace isn’t a git repository — Maestro builds on worktree branches.”
No other run in flightRefused — finish or kill the active run in the Maestro room first. One run per workspace.
At least one agent CLI on PATHThe Start button stays disabled.

The states a run moves through

The status is stored, not held in memory. Every transition is an atomic claim, so the driver and a background actor can never advance the same run at once — and the status survives quitting the app.

StateWhat is happeningWhat moves it on
intakeThe brain proposes up to four clickable questions about your command.You answer or skip. Zero questions auto-advances.
planningThe brain decomposes the command into worker tasks.The plan parses and passes validation.
awaiting_planThe plan gate. Nothing has launched.You press Approve & launch. Nothing else.
dispatchingWorktrees and panes are created, CLIs launched.The first wave is running.
runningWorkers build. The sentinel watches their panes.Every worker in the wave settles, then its reviews run.
integratingWorker branches merge onto the integration branch.All merges clean. A conflict pauses the run.
verifyingYour configured checks run against the merged result.Checks finish.
testingThe browser-test gate — see below.The gate runs, or skips itself.
reportingThe report is rendered and written to disk.Report saved.
done / failed / cancelledTerminal. The report stays on screen.New run.
pausedHeld by you, by a merge conflict, or by a worker that review rejected twice.Resume.

The plan gate

A run parks at awaiting_plan and stays there. No worktree is created, no CLI is launched, nothing is written to your repo until you approve. This is the only human gate the driver will never skip — on restart, a run at the gate is adopted read-only and waits for you again.

At the gate you can change these, per task, before approving:

  • Title — the task text the worker receives.
  • Provider — which installed CLI runs it.
  • Model — Auto, or a specific Claude model for Claude workers.
  • Effort — Auto, Std, High or Ultra.
  • Drop it — the task is skipped, and so is its injected reviewer.
  • Worker autonomy for the whole run — Walk-away, Accept edits or Ask.

Approve is disabled if you drop every worker. The gate also shows two warnings when they apply: which tasks were reassigned because the brain picked a provider you have not installed, and which reviews fell back to the same model that wrote the code.

A plan the brain returns is validated before it reaches the gate. These rejections fail the run rather than silently degrading it:

  • At least one task; keys unique and non-empty.
  • depends_on may only name other keys in the same plan. Dependency cycles are rejected.
  • Keys r1, r2 … are reserved for injected reviewers. A plan using one is rejected.
  • A plan task of kind review is rejected — reviewers are injected by the app, never authored by the brain. A brain-written reviewer would collide with the real one and quietly disable cross-model review for that worker.

Routing hints are the exception: an unusable model or effort value degrades to the provider default instead of failing the plan.

Workers

Each approved task gets a branch maestro/<run-id>/<key>, a git worktree on that branch, and a pane. Your own checkout is never touched — merges happen inside a separate integration worktree.

SettingDefaultRange
Max workers41–12, but the pane grid holds 16 panes total
Max rounds31–10
Time budgetBalancedFast, Balanced, Thorough — shapes how heavily the planner routes models
Worker autonomyWalk-awayWalk-away, Accept edits, Ask

Workers report progress on their own stdout using protocol lines — SIGMA::STATUS, SIGMA::BLOCKED, SIGMA::DONE. The launch prompt also tells each worker to stay inside its owned files, and not to drive a browser, run end-to-end tests, or leave a dev server running: verification belongs to the run’s gates, not to a worker.

Workers never launch in plan mode

Whichever autonomy you pick, plan mode is pinned off. A worker launched in plan mode would sit at its CLI’s plan-approval prompt with nobody to approve it. Ask has a related cost: every permission prompt lands in that worker’s pane and you shepherd each one by hand. The gate warns you when Ask is selected.

The sentinel

A background watcher polls every active run’s worker panes every two seconds and classifies the terminal tail: exited, done, blocked, waiting for input, working, idle at a prompt, or quiet and unknown. It then walks a fixed escalation ladder.

ConditionWhat the sentinel doesLimit
A SIGMA::DONE seen only in the terminal tailHolds it for 15 s before marking the task doneReset if the worker starts working again
Idle for 60 sNudges the worker2 nudges per round
Still idle 120 s after a nudgeAsks the brain to judge whether it is stuckOnce per 10 minutes per task
Unresolved for 30 minutesMarks the task needs_attention

The sentinel never kills a process and never marks a task failed. A worker’s own SIGMA::DONE on the message bus outranks anything the classifier thinks, and protocol lines echoed back from the launch prompt are suppressed rather than read as verdicts.

Cross-vendor review

For any review policy but off, Maestro injects one reviewer task per worker task — r3 reviews w3. The reviewer is chosen to be a different model family from the one that wrote the code. The worker’s own provider is always last in the preference order, used only when nothing else is installed.

Worker ran onReviewer preference, first installed wins
claudecodex → agy → gemini → grok → opencode → qwen → claude
codexclaude → agy → gemini → grok → codex
geminiclaude → codex → grok → opencode → qwen → agy → gemini
agyclaude → codex → grok → opencode → qwen → gemini → agy
anything elseclaude → codex → agy → gemini → itself

agy and gemini are collapsed into one family: agy replaced the retired gemini CLI, so one reviewing the other is the same model wearing a different binary name. It is reported as degraded, not as diversity.

Five installed CLIs are never chosen as reviewers

Aider, Cursor Agent, Droid, Copilot CLI and Amp appear in no preference list. A machine with only Claude Code and Aider installed will review Claude’s work with Claude, not Aider. That is a same-model review: the plan gate flags it same-model, the run log flags it, and the report carries the hint to install codex, agy or grok to widen the pool.

The review still runs, and still gates the work. It is just not cross-model, which is the property the feature exists for.

How a review actually runs

  1. The worker’s worktree is committed, then diffed against the run’s integration branch — so a second-round review never re-reads work that already merged.
  2. A diff over 60,000 characters is truncated, with a marker where it was cut.
  3. The reviewer CLI is run headlessly, one shot, as <cli> -p "<prompt>". For Claude, --permission-mode plan is added so the reviewer cannot write. The reviewer is told it cannot modify files.
  4. The run is capped at 600 seconds.

The verdict contract is strict: the first line that is exactly APPROVED or CHANGES_REQUESTED. Anything else — a timeout, a non-zero exit, a reviewer that answered in prose — is a parse error, and a parse error is never an approval. It is recorded as changes requested with the reason attached.

One exception is an outright approval: a worker branch with an empty diff approves as “no changes on the worker branch — nothing to review”. Verification and the done predicate still stand between a no-op worker and a finished run.

When a review comes back with changes requested, the replan brain picks the cheapest path to green: redispatch (the default — the findings are typed into the worker’s still-live pane rather than relaunching it), replan, or give up. A worker rejected twice is marked needs_attention and the run pauses for you.

What “done” requires

The decision is a single pure predicate, not a judgement call. Every gate must be green in the same round.

GateSatisfied by
WorkersEvery non-skipped worker reported done. Zero workers can never pass.
ReviewsWhen reviews are required: at least one review exists and all of them approved. Zero reviews can never satisfy a required-review gate.
VerifyA configured check suite must have passed. An unconfigured one must merely not have failed.
Browser testPer policy — see the next section. An unrecognised policy value fails the run instead of quietly downgrading.

If a gate is red and rounds remain, the run loops: replan or redispatch, build again, re-review, re-verify. Hitting the round cap or the deadline fails the run — but a round that turns everything green at the cap still counts as done.

The browser-test gate does not fire

Read this before relying on the e2e gate

New runs are created with the browser-test policy set to if available, and the gate needs two things: a DeckTest engine present, and a web_url on the run for it to point at. No surface in the app sets web_url — not the Maestro room’s setup form, not the voice entry, not the Orchestrator’s plan card. The field exists and is patchable through the run API, but nothing writes it.

So in practice the gate logs “no web_url on the run — e2e skipped” and the run finishes on the worker, review and verify gates alone. Treat the browser test as not yet wired up, and do not read a green run as evidence that anything was exercised in a browser.

There is a second value with the same problem in a smaller way. The review policy accepts required, if_diverse and off, but nothing reads if_diverse: reviewers are injected for it exactly as for required, while the final done predicate only demands approvals when the policy is literally required. It is not selectable in the app, and new runs are always created as required.

What happens when you restart

A run left mid-flight is picked up from its stored status. Only the newest non-terminal run in the workspace is adopted, and the driver re-attaches only for states where the next action is implied by the database.

Status when the app closedOn relaunch
dispatching, runningDriver re-attaches and resumes walking the waves.
integratingDriver re-attaches at integration.
verifying, testingDriver re-attaches at verify and re-runs it. Verify results are not persisted, so it re-measures rather than inventing a verdict.
reportingDriver finishes the report from the stored row.
awaiting_planAdopted read-only. The gate waits for your approval, as it did before.
pausedAdopted read-only. Resume re-attaches the driver.
intakeAdopted read-only. The questions were not durable — kill it and start a fresh run.
planningAdopted read-only. The brain call was in flight — kill it and start a fresh run.
done, failed, cancelledNothing to resume.

Worker panes are a separate question from the run. Reloading the app’s window leaves the worker terminals alive in the backend, and the driver rebinds a pane to each one so you can watch them again. If a terminal is gone, the row is dispatched again into the same worktree and branch, so the work already on disk is not lost.

A lapsed subscription stops the driver, not the data

Re-attaching is itself Pro-gated. Without an active subscription the run is adopted for display with “the driver was not re-attached”, and its worktrees, branches and stored status stay exactly where they are.

What you are left with

  • An integration branch, maestro/<run-id>/integration, holding the merged result. Nothing is merged into your working branch — the report gives you the git merge --no-ff line to run yourself.
  • A report written to .deckspace/maestro/<run-id>/report.md in your repo, and shown in the room. It is rendered deterministically from the run’s facts; a model may narrate it, but never invents it.
  • Worker transcripts under .deckspace/maestro/<run-id>/transcripts/.
  • Every worktree, including after the kill switch. The kill switch stops all worker terminals and cancels the run, and deliberately preserves the worktrees for a post-mortem.

A note on the brain

The planning, intake, judging, replanning and reporting calls all go through one model, chosen per run. The Maestro room’s Brain dropdown defaults to Gemini; starting a run by voice or from the Orchestrator’s plan card defaults to the headless Claude Code CLI instead. An API brain needs its key in Settings, or the run fails with “brain ‘…’ has no API key configured”.

One brain call is capped at 300 seconds. If a call comes back with a rate, quota or usage-limit error, Maestro fails over to whichever of Gemini, OpenAI, Anthropic and Groq you have keys for. Intake is the only call allowed to fail quietly: a flaky brain there means the run plans without your answers rather than stopping.

The reviewer one-shot is confined by the same OS sandbox as the rest of the app, when one is available.

PlatformReviewer sandbox
macOSbuilt in sandbox-exec
Linuxsupported bwrap, if installed
Windowsno sandbox the reviewer runs unconfined