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.
| Requirement | What happens without it |
|---|---|
| An active Pro subscription | Refused: “Maestro orchestration is a Pro feature.” |
| An open workspace | Refused: “Open a workspace first.” |
| That workspace is a git repository | Refused: “The workspace isn’t a git repository — Maestro builds on worktree branches.” |
| No other run in flight | Refused — finish or kill the active run in the Maestro room first. One run per workspace. |
| At least one agent CLI on PATH | The 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.
| State | What is happening | What moves it on |
|---|---|---|
intake | The brain proposes up to four clickable questions about your command. | You answer or skip. Zero questions auto-advances. |
planning | The brain decomposes the command into worker tasks. | The plan parses and passes validation. |
awaiting_plan | The plan gate. Nothing has launched. | You press Approve & launch. Nothing else. |
dispatching | Worktrees and panes are created, CLIs launched. | The first wave is running. |
running | Workers build. The sentinel watches their panes. | Every worker in the wave settles, then its reviews run. |
integrating | Worker branches merge onto the integration branch. | All merges clean. A conflict pauses the run. |
verifying | Your configured checks run against the merged result. | Checks finish. |
testing | The browser-test gate — see below. | The gate runs, or skips itself. |
reporting | The report is rendered and written to disk. | Report saved. |
done / failed / cancelled | Terminal. The report stays on screen. | New run. |
paused | Held 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_onmay 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
reviewis 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.
| Setting | Default | Range |
|---|---|---|
| Max workers | 4 | 1–12, but the pane grid holds 16 panes total |
| Max rounds | 3 | 1–10 |
| Time budget | Balanced | Fast, Balanced, Thorough — shapes how heavily the planner routes models |
| Worker autonomy | Walk-away | Walk-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.
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.
| Condition | What the sentinel does | Limit |
|---|---|---|
A SIGMA::DONE seen only in the terminal tail | Holds it for 15 s before marking the task done | Reset if the worker starts working again |
| Idle for 60 s | Nudges the worker | 2 nudges per round |
| Still idle 120 s after a nudge | Asks the brain to judge whether it is stuck | Once per 10 minutes per task |
| Unresolved for 30 minutes | Marks 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 on | Reviewer preference, first installed wins |
|---|---|
| claude | codex → agy → gemini → grok → opencode → qwen → claude |
| codex | claude → agy → gemini → grok → codex |
| gemini | claude → codex → grok → opencode → qwen → agy → gemini |
| agy | claude → codex → grok → opencode → qwen → gemini → agy |
| anything else | claude → 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.
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
- 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.
- A diff over 60,000 characters is truncated, with a marker where it was cut.
- The reviewer CLI is run headlessly, one shot, as
<cli> -p "<prompt>". For Claude,--permission-mode planis added so the reviewer cannot write. The reviewer is told it cannot modify files. - 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.
| Gate | Satisfied by |
|---|---|
| Workers | Every non-skipped worker reported done. Zero workers can never pass. |
| Reviews | When reviews are required: at least one review exists and all of them approved. Zero reviews can never satisfy a required-review gate. |
| Verify | A configured check suite must have passed. An unconfigured one must merely not have failed. |
| Browser test | Per 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
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 closed | On relaunch |
|---|---|
dispatching, running | Driver re-attaches and resumes walking the waves. |
integrating | Driver re-attaches at integration. |
verifying, testing | Driver re-attaches at verify and re-runs it. Verify results are not persisted, so it re-measures rather than inventing a verdict. |
reporting | Driver finishes the report from the stored row. |
awaiting_plan | Adopted read-only. The gate waits for your approval, as it did before. |
paused | Adopted read-only. Resume re-attaches the driver. |
intake | Adopted read-only. The questions were not durable — kill it and start a fresh run. |
planning | Adopted read-only. The brain call was in flight — kill it and start a fresh run. |
done, failed, cancelled | Nothing 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.
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 thegit merge --no-ffline to run yourself. - A report written to
.deckspace/maestro/<run-id>/report.mdin 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.
| Platform | Reviewer sandbox |
|---|---|
| macOS | built in sandbox-exec |
| Linux | supported bwrap, if installed |
| Windows | no sandbox the reviewer runs unconfined |