Skip to main content
Glama

Maestro

Run a dozen Claude Code sessions at once, and let one of them conduct the rest.

Each agent is a full Claude Code process in a terminal of its own — not a subagent, not an API call. Maestro launches them, reads their screens to know what they are doing, carries messages between them, and draws the whole fleet on a live panel.

Latest release Python Platform License

Real recording: eight agents auditing this repository. Each wire lights up when a message crosses it, the mark pulses as a new agent joins, and the ticker carries what they just reported.


What it does

Claude Code can spawn subagents, but a subagent shares your context window and dies with your turn. Maestro runs real sessions instead: separate processes, separate context, separate conversation, all alive at the same time. You keep working in your editor while eight of them build eight parts of your project.

The orchestrator can be Claude itself. Point Claude Code at the maestro MCP server and it can launch workers, hand them briefs, poll their status, read their answers, answer their questions and close them — from the session you are already talking to. Or drive it by hand with maestro status and the CLI.

Knowing what an agent is doing is the hard part, and Maestro does it the way a person would: it reads the rendered terminal. A spinner means working, an option list means it is waiting for an answer, a finished response means the turn is done. No log parsing, no hooks, no cooperation needed from the agent.

Related MCP server: Claude Team MCP Server

Features

  • Full sessions, not subagents — one Claude Code process per worker, in its own terminal, with its own context and its own conversation

  • Agents that delegate — every session gets an MCP bridge: assign a worker without waiting, handoff and block for the answer, send_message to reply to whoever called you

  • Unstick a worker without touching the keyboard — answer_prompt answers the question it is blocked on, interrupt stops a turn that is going in circles, restart_terminal gives it a fresh Claude with the same id, directory and queue

  • Isolated git worktrees — use_worktree puts a worker on its own branch in its own checkout, so ten agents on one repository never trample each other; merge with plain git when they are done, and nothing uncommitted is ever thrown away

  • Live panel — the fleet as a ring of agents around the conductor, wires that light up when a message crosses, a ticker of what just happened, and the state of every agent in its glyph. Installs as a desktop app (PWA); works offline

  • One project at a time — running two projects at once no longer piles every agent onto a single ring. Each working directory gets the field to itself; drag the field sideways like a slider (or scroll sideways, or press the arrow keys) and the next project's page slides in. The projects you are not watching wait at the edges as breathing arrowheads, one each, warming to orange when a worker there needs an answer and to red when one has failed

    Demo mode (?demo), three projects: drag to the next one and back, then the two hovers.

  • It tells you when it needs you — system notifications when an agent asks a question, dies, or freezes for ten minutes with nothing changing on screen

  • Progress you can trust — agents report their own percentage (report_progress); hover a worker to read it, hover the mark for the fleet's mean. No report, no invented number

  • Survives a restart — on Linux and macOS the server re-adopts the tmux sessions that are still alive; everywhere it keeps a note of worktrees whose agent is gone so their work can still be merged

  • Agents that can reach further — maestro mcp import shares the MCP servers from your own Claude Code with every agent, so a worker can drive a browser or read an issue instead of only editing files. They run with --strict-mcp-config, so they get exactly what you share and nothing else

  • Stays out of your way — agents run below normal CPU priority, so a fleet at full tilt never takes your editor with it; starts are staggered; the fleet lives on a tmux server of its own, out of reach of a stray tmux kill-server

  • Small enough to read — under 4,000 lines of Python, two dependencies (four on Windows), no database, no web framework

Installation / Usage

Windows — one click

Download MaestroSetup.exe and run it. Nothing to type, no administrator rights, no restart, no WSL: Maestro runs natively on Windows. The installer sets up what is missing and skips what you already have:

  1. Git for Windows (per user, checksum-verified) — Claude Code needs its Bash.

  2. uv, which brings its own Python.

  3. Claude Code, from Anthropic's own installer.

  4. Maestro, with its profiles, the skill and the MCP server, so Claude Code in VS Code or the terminal can use it straight away.

  5. Your Claude account — a window opens and your browser signs you in.

  6. The panel opens in a window of its own; the Start menu entry Maestro reopens it.

The setup file itself is about 2 MB. On a machine with nothing installed it downloads about 200 MB in total (Git, Python and Claude Code); on one that already has them, a few MB.

The installer is not code-signed yet, so Windows may show "Windows protected your PC": click More info → Run anyway. Uninstall it from Settings → Apps like anything else; Git, uv and Claude Code stay, since other tools may use them.

macOS and Linux — one line

curl -fsSL https://raw.githubusercontent.com/daniel-madrid-07/Maestro/main/install.sh | bash

Installs tmux (through your package manager or Homebrew), uv, Claude Code and Maestro, signs you in to Claude if needed, and starts the panel. Run it again at any time: it only does what is missing.

By hand

uv tool install git+https://github.com/daniel-madrid-07/Maestro
maestro init      # config, profiles, the skill, and the MCP server in Claude Code
maestro doctor    # checks the terminals, claude, sign-in, the port and the registration
maestro up        # starts the server and opens the panel

The same four lines work in PowerShell on Windows.

Using it

In any project, tell Claude Code what you want built:

Orchestrate this with Maestro: split it into as many workers as it genuinely divides into, review at the end, and close the sessions when you are done.

Claude launches the fleet, and you watch it on http://127.0.0.1:9889.

Commands

maestro up

start the server, open the panel (--foreground, --no-open)

maestro status

every session and terminal, with status, progress and branch (--json)

maestro doctor

what is missing and how to fix it

maestro open

open the panel (on Windows, in a window of its own)

maestro down

stop the server; with tmux the sessions stay up, on Windows they end with it

maestro init

set up ~/.maestro, the profiles, the skill, the scout subagent and the MCP registration (--force)

maestro mcp list / import

the MCP servers every agent gets, taken from your own Claude Code if you like

Requirements

Python

3.11 or newer

Claude Code

installed and signed in (claude on PATH)

OS

Windows 10 1809 or newer, macOS, or Linux

Terminals

Windows: nothing to install (each agent gets a ConPTY, Windows' own pseudo-console). macOS and Linux: tmux 3.0 or newer

Profiles

An agent profile is a Markdown file with YAML front matter: model, effort, MCP servers and the system prompt appended to Claude's own. Three ship with Maestro — worker (delegates lookups to a cheap scout subagent), reviewer, and code_supervisor (runs its own workers). Copies land in ~/.maestro/profiles/, and a file you edit there wins over the packaged one.

Architecture

  • maestro-server — the HTTP API. Owns the terminals (tmux sessions, or ConPTYs on Windows), starts Claude in each one, reads every screen once a second to classify its state, delivers queued messages when an agent is free, streams events over SSE, and serves the panel.

  • maestro-ops — the MCP server the outside orchestrator (Claude Code in your editor) talks to, registered as maestro: fleet_status (everything at once, plus who needs answering), wait_for (blocks until a worker finishes, asks or dies, instead of polling), launch_sessions (a whole fleet in one parallel call), launch_session, broadcast_message, get_usage, send_session_message, read_session_output, get_terminal_status, get_terminal_output, answer_prompt, interrupt, restart_terminal, list_sessions, get_session_info, list_profiles, get_profile_details, list_worktrees, remove_worktree, shutdown_session.

  • maestro-agent — the MCP server every launched session gets, so agents can build their own sub-fleets: assign, handoff, send_message, report_progress, list_terminals, get_terminal_status, get_terminal_output, answer_prompt, interrupt, restart_terminal, delete_terminal.

  • The panel — a single HTML file served by the server: canvas for the field and the wires, DOM for the labels, SSE for the traffic.

State lives in ~/.maestro/ (config, profiles, logs, worktrees, state.json). Nothing leaves your machine except Claude Code's own traffic.

The API binds loopback and has no authentication, because it is a local tool. It sends no CORS headers and refuses any request carrying a foreign Origin, so a page you happen to have open in your browser cannot reach it. Do not expose the port.

Tech stack

Python 3.11 (standard library — http.server, subprocess, threading), tmux on Linux and macOS, pywinpty and pyte on Windows, Claude Code, the MCP Python SDK, PyYAML, and plain HTML/Canvas for the panel.

License

MIT — see LICENSE. The terminal-state heuristics are adapted from awslabs/cli-agent-orchestrator (Apache-2.0); see NOTICE.

Available Tools

20 tools
answer_promptA

Answer the question a terminal is blocked on. Only valid while its status is waiting_user_answer.

answer is a key (Enter, Escape, Up, Down, Tab, Space), a single digit (in an option dialog it selects and submits that option: "2" = the second one), or one line of text, typed and followed by Enter — only where a text field has focus (an option list treats letters as hotkeys). Escape cancels the question and ends the turn with no answer.

Inputs: answer (required), terminal_id (required). Returns: object

broadcast_messageA

Queue one message for many terminals at once (the whole fleet when none is named).

For the things you say to everyone: a change of plan, "commit what you have and report", a constraint you forgot. Each terminal receives it when it is next idle, exactly as send_session_message does.

Inputs: message (required), terminal_ids, session_names. Returns: object

fleet_statusA

The whole fleet in one call: every session, every terminal, and who needs you.

Prefer this over list_sessions plus a get_session_info per session: it is one call and one block of context instead of one per session. Each terminal carries status, progress, stuck, model, profile, working directory, worktree and pending_messages. needs_attention is the part to act on -- terminals that are waiting on an answer, stuck, or failed. totals counts them by status.

Inputs: no named parameters. Returns: object

get_profile_detailsB

Return a profile's full system prompt and settings.

Inputs: profile_name (required). Returns: object

get_session_infoC

A session and every terminal in it, with live statuses (oldest terminal first).

Inputs: session_name (required). Returns: object

get_terminal_outputB

Read a terminal's output. last = Claude's final answer; full = the rendered scrollback.

Inputs: terminal_id (required), mode. Returns: object

get_terminal_statusB

Live status of one terminal: idle, processing, completed, waiting_user_answer, error.

Inputs: terminal_id (required). Returns: object

get_usageA

How much of the Claude subscription is left: the 5-hour and weekly windows.

Worth a look before opening a large fleet, and when deciding whether to run the stronger model everywhere. Percentages are utilisation, so 100 means spent; resets_at says when the window rolls over. Unavailable (no local credentials, or the endpoint refused) comes back as ok: false rather than a made-up number.

Inputs: no named parameters. Returns: object

interruptA

Stop a terminal's current turn (Escape), e.g. when a worker is going in circles.

Only while its status is processing or waiting_user_answer (an idle prompt has nothing to interrupt and is refused). Returns the status observed ~2 s later (normally idle or completed); queued messages are then delivered as usual.

Inputs: terminal_id (required). Returns: object

launch_sessionA

Start a new session: one full Claude Code process in its own tmux window.

Blocks until Claude shows its input box (up to ~90 s) so a failed start is reported here rather than discovered later. working_directory must be a Linux path (/mnt/c/... for a Windows folder). model overrides the profile's model (opus / sonnet). initial_message is delivered as the first task once the session is ready. provider is accepted for compatibility; only claude_code exists.

use_worktree runs the session in its own git worktree (under MAESTRO_HOME) on a new branch mx/<terminal_id> made from the repo's HEAD, so parallel workers on one repository never trample each other; the result's worktree gives its path and branch. The worker is told to commit there. Merge it yourself with plain git afterwards (git -C <repo> merge mx/<terminal_id>). Deleting the terminal removes the checkout after committing anything left uncommitted on the branch; the branch is kept when it has commits the repo's HEAD lacks.

Inputs: agent_profile (required), model, provider, session_name, +3 more. Returns: object

launch_sessionsA

Launch several sessions at once, in parallel. One call for a whole fleet.

Each entry takes the same fields as launch_session: agent_profile, session_name, working_directory, model, initial_message, use_worktree. Every launch still blocks until its Claude is ready, so a failure is reported against its own entry and the rest still start; max_parallel (1-8) caps how many start at the same time.

Ten sessions launched one by one cost ten calls and several minutes of waiting; this is one call and about as long as the slowest one.

Inputs: sessions (required), max_parallel. Returns: object

list_profilesA

List the agent profiles a session can be launched with (name, role, model, effort).

Inputs: no named parameters. Returns: object

list_sessionsB

All running sessions with their terminal counts.

Inputs: no named parameters. Returns: object

list_worktreesA

Every live terminal that runs in its own git worktree: what there is to merge.

Each entry has terminal_id, status, repo, path and branch. Merge a finished one with git -C <repo> merge <branch>; deleting the terminal commits any leftovers on the branch, removes the checkout and keeps the branch when it has commits the repo's HEAD lacks. orphaned lists checkouts whose terminal died with an earlier server; they are never removed on their own — merge what you want, then remove_worktree.

Inputs: no named parameters. Returns: object

read_session_outputB

Read output by terminal id, or by session name when the session has one terminal.

max_chars keeps only the tail of the output.

Inputs: mode, max_chars, terminal_id, session_name. Returns: object

remove_worktreeA

Remove an orphaned checkout listed by list_worktrees (leftovers are committed first; the branch stays if unmerged).

Inputs: terminal_id (required). Returns: object

restart_terminalA

Replace a terminal's Claude with a fresh one: same id, profile, model, directory and inbox.

Use when its status is error (Claude exited) or it stays unresponsive after interrupt. The conversation is lost. Blocks until the new Claude is ready (up to ~90 s).

Inputs: terminal_id (required). Returns: object

send_session_messageB

Queue a message for a terminal; it is pasted as soon as the terminal is idle.

Inputs: message (required), terminal_id (required). Returns: object

shutdown_sessionB

Close every terminal of a session and the session itself.

Inputs: session_name (required). Returns: object

wait_forA

Block until a terminal finishes, asks something or dies -- instead of polling.

This holds your conversation until it returns. From Claude Code, prefer a background watch on GET /events (see the maestro skill) so the user can keep talking to you; use this for scripts, or with a short timeout.

Returns as soon as one watched terminal reaches one of states (default: completed, waiting_user_answer, error -- stuck is also available), or when timeout_seconds runs out (max 900). With require_all it waits for every watched terminal instead of the first. terminal_ids defaults to the whole fleet. A terminal with a message still queued counts as busy, not finished.

This is the loop to use while a fleet runs: wait, act on what came back (read its output, answer its question, restart it), wait again. Polling get_terminal_status in a loop costs a call and a turn every few seconds and tells you nothing in between.

Inputs: states, require_all, terminal_ids, timeout_seconds. Returns: object

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.5/5.0

    Scored across 20 tools

    Disambiguation3/5

    Several tools overlap: get_terminal_output and read_session_output both read terminal output, get_terminal_status/list_sessions/get_session_info/fleet_status all report status, and send_session_message vs broadcast_message differ only in cardinality. The descriptions do a good job steering the agent toward the aggregate tools (fleet_status, wait_for, launch_sessions), which mitigates but does not eliminate the ambiguity.

    Naming Consistency4/5

    Mostly consistent verb_noun snake_case (list_profiles, launch_session, send_session_message, shutdown_session, remove_worktree). A few outliers break the pattern: interrupt, wait_for, and fleet_status are not verb_noun, but naming remains readable and predictable overall.

    Tool Count4/5

    20 tools for an agent-fleet orchestration server is on the heavy side but justified by the breadth of the domain (profiles, launch, messaging, monitoring, worktrees, usage). Aggregate tools like launch_sessions, broadcast_message, and fleet_status absorb many one-off calls, so the count is mostly earned.

    Completeness4/5

    The surface covers the full lifecycle: profile discovery, session/terminal launch, messaging, monitoring, interruption, restart, shutdown, worktree management, and usage. Minor gaps like an explicit kill/delete-terminal tool (only shutdown_session exists) and worktree merge automation are workable around with plain git.

    Maintenance

    ActivityNo data
    ResponsivenessSyncing

    Related MCP Connectors

    Related MCP Servers