An orchestrator/implementer loop for Claude Code — Opus plans, debates, and reviews; Codex writes the code and proves it works.
The orchestrator thinks through what and how — architecture, plans, reviews, talking to you — but never touches source files. Codex, dispatched through a watchdog, writes the code, runs the verification, and returns a structured result: evidence, or the questions it needs answered.
It runs on your existing ChatGPT Plus / Codex login. No OpenAI API key, no proxy, no second subscription.
git clone https://github.com/Pythoughts-labs/maestro.git
cd maestro && node install.mjsRestart Claude Code (plain
claude) afterward so the rules and hooks load.
Run node install.mjs, restart Claude Code in your project terminal, then ask Claude directly to use Codex for the task. That's it — no Maestro skill or slash command is needed.
Some teams trust Claude's judgment more than its patience, and Codex's typing more than its plans. Maestro is for that split: the model you want making decisions (Opus 5 / Fable 5) holds the plan and the final review; the model you want grinding through edits holds the pen.
The loop also fixes what breaks in practice — Codex hanging mid-task, an implementer that guesses when the plan is ambiguous, a review that rubber-stamps its own plan.
session start → pick model + debate/write efforts (or keep a complete pin) ← the setup
your prompt
→ design fork or murky bug? Opus DEBATES Codex (read-only, transcript-carried) ← the argument
grill and be grilled, until CONVERGED / ESCALATE / 6-turn cap
→ Opus plans it: objective, files, steps, constraints, verification commands ← the brain
→ the AUTONOMOUS LOOP runs Codex (write-enabled) until verified: ← the hands
dispatch → RESULT → local re-verification → failure evidence re-dispatched
exits only: VERIFIED_DONE / NEEDS_ANSWERS / BLOCKED / STUCK-at-cap
→ questions relayed back to you, answers re-dispatched ← the loop
→ stuck with unclear root cause? the attempts log goes to the debate table
→ Opus reviews the actual diff (SHIP / FIX-FIRST / RETHINK)
→ done
The loop never ends in prose. Every run finishes on a machine-readable state:
| Code | State | What it means | What you do |
|---|---|---|---|
0 |
VERIFIED_DONE | Plan executed and the verify command passed locally | Review the diff — a claim is not proof |
10 |
NEEDS_ANSWERS | Codex hit real ambiguity and stopped instead of guessing | Answer the QUESTIONS: block, re-run |
11 |
BLOCKED | Missing access, a destructive step, lease contention, or an unconfirmed cancelled writer | Surface it; never improvise around it |
12 |
STUCK | Hit the iteration cap without verification | Read the attempts log, re-plan — don't just raise the cap |
These codes describe implementer-loop.sh. A directly invoked single-shot watchdog uses rc 125 plus MAESTRO_FINAL: WATCHDOG POISONED when cancellation leaves quiescence unconfirmed; the outer loop translates that condition to BLOCKED/11.
Write contention waits without arrival ordering only while the current lease has a confirmed release path. MAESTRO_LOCK_WAIT_SEC caps the wait (default 300 seconds; 0 disables it), and MAESTRO_LOCK_WAIT_POLL_SEC controls polling (default 5 seconds, minimum 1); invalid values disable waiting.
Foreground write supervisors update a separate lease heartbeat every MAESTRO_LOCK_HEARTBEAT_INTERVAL_SEC (default 20 seconds, minimum 1; invalid values use 20). MAESTRO_LOCK_HEARTBEAT_STALE_SEC controls when a missed heartbeat is reported (default 90 seconds; 0 disables staleness reporting; invalid values use 90). A stale heartbeat is only a recovery candidate: --clear-lease still refuses while the recorded owner process is alive or unidentifiable, or any repository-global companion writer is visible.
MAESTRO_MAX_DISPATCH_SEC is a hard ceiling: unset write jobs get 2400 seconds and read-only discussions get 1200; an explicit valid value is used exactly, while invalid input warns and falls back to 1200. Startup consumes this budget, poll sleeps are clipped to the nearest deadline, and one halfway warning continues the same job without claiming progress or creating a checkpoint. Idle time uses elapsed monotonic time rather than configured poll counts. --max-idle and --poll must be positive integers and are rejected before any lease or task starts. The local verifier has its own process-group deadline (MAESTRO_VERIFY_TIMEOUT_SEC, default 900 seconds), and MAESTRO_COMPANION_TIMEOUT_SEC bounds each companion call (default 120 seconds). Four consecutive empty or malformed statuses, or the hard ceiling during status loss, cancel and fail closed. Read-only status loss consumes its configured retry allowance; idle/deadline cancellation does not. A write cancellation—including one reported externally by the companion—poisons and retains the lease, ends the loop as BLOCKED, emits UNREPORTED_PARTIAL at the hard ceiling, and never starts a replacement writer. Once no Codex job is writing, recover with bash hooks/implementer-loop.sh --clear-lease (installed: bash ~/.claude/hooks/implementer-loop.sh --clear-lease). A metadata-less lease younger than five seconds is treated as an owner still initializing, not an orphan to clear.
On every SessionStart source, the hook appends a validated MAESTRO_SESSION_ID export to $CLAUDE_ENV_FILE. The value is attribution only: the token, PID/process-start identity, and companion job liveness remain the ownership checks. A missing or invalid value is recorded as unknown; the session appears in lease metadata, contention/poison messages, and provenance records.
Installed under ~/.claude, plus one shared library they source.
| File | Role |
|---|---|
session-start.mjs |
Opens each session with model plus separate debate/write effort picks. Resumed sessions get a status line instead of a re-ask. |
codex-model-select.sh |
Serializes concurrent selectors and transactionally pins model plus debate/implementation effort, preserving config modes and top-level TOML scope. |
codex-mcp-check.sh |
Shows exactly which MCP servers your background Codex jobs inherit, env keys masked. |
implementer-loop.sh |
The autonomous heart: dispatch → parse RESULT → re-verify locally → feed failure evidence back in. Bounded by --max-iters and a verifier deadline. |
discussion-loop.sh |
Read-only debate with collision-resistant workspace identity, private transcripts, sidecar turn state, and stale-lock recovery. |
implementer-watchdog.sh |
Single dispatch with --write; idle or absolute cancellation poisons and retains its write lease. |
orchestrator-inject.mjs |
Resets the direct-edit flag per task; states the loop only when the prompt carries a code/design signal. |
orchestrator-gate.mjs |
Blocks the orchestrator's Edit/Write/MultiEdit on source files. |
lib-companion.sh |
Shared library: the write lease, provenance detection, companion resolution. |
Plus the behavioral specs: orchestrator-implementer.md (read every session) and coding-discipline.md (useful standalone).
Grilling is structured, not vibes
Multi-turn debate between models fails in two known ways: endless courteous loops, and one side caving to sound cooperative. A mandatory stance line (AGREE / PUSHBACK / ALTERNATIVE / REFRAME) forces Codex to commit each turn; an AGREE still has to name the assumption most likely to be wrong; REFRAME gives it explicit license to reject the question itself. A 6-turn cap forces every debate to land on CONVERGED with stated assumptions, or ESCALATE to you with both cases intact.
The output isn't lost either — the converged design, the losing alternatives, and their rejection reasons become the Decisions section of the plan, so the implementer sees the debate it wasn't part of.
Write access is scoped by contract, reviewed by diff
Codex really edits your tree — that's the point. It may touch only the files the plan names, must report every file it changed, and nothing it does is believed until the orchestrator re-reads the actual git diff against the stated goal. Before its first edit, it must run git status --short, report the pre-existing dirty paths, preserve them, and confirm out-of-scope paths stayed untouched; this is an obligation and report, never a dirty-tree gate. Fixes never get silently patched by the orchestrator; they go back to Codex so the diff stays single-author.
Questions are a first-class channel, not a failure
A background job can't ask interactively, so the contract gives it a structured way to stop instead of guess: RESULT: NEEDS_ANSWERS plus a numbered QUESTIONS: block. An implementer that guesses is worse than one that asks.
The final review is mandatory, and honest about being same-vendor
The orchestrator reviews its own plan's execution. The spec says so out loud and compensates: fresh-eyes diff read, re-run the cheap verification yourself, check for scope creep, open with SHIP / FIX-FIRST / RETHINK. If you want a cross-vendor review, pair Maestro with a separate read-only advisor for the review step only.
Codex's built-in web search is disabled — that was the thing that kept hanging. Its configured MCP servers (tavily, context7, …) stay available to both loops for version-sensitive facts.
Research flows plan-first: the orchestrator pre-researches and embeds facts before dispatching, and Codex verifies only what turns out version-sensitive — capped at 2 lookups per run, no retries on stall. Verified facts come back labeled verified via <mcp>: <fact>, so the reviewer can trust them over either model's training data.
bash tests/run.shSixteen suites cover leases, liveness, installation ownership, model selection, gate authorization, discussions, provenance, and nested process cleanup. They drive real entry points end to end—acquiring leases, mutating repositories, signalling supervisors, and installing into isolated homes—rather than replacing lifecycle behavior with mocks.
They are slow on purpose: several suites wait on real lease timeouts.
- Claude Code (ships Node), with Opus 5 (
claude-opus-5) or Fable 5 (claude-fable-5) as the session model — a/modelchoice, not a config here. - The Codex plugin —
/plugin install codex@openai-codexinside Claude Code. - A ChatGPT Plus / Codex login —
codex login. Not an API key. - Optional, for
--with-workflow:/plugin install ralph-loop@claude-plugins-official.
git clone https://github.com/Pythoughts-labs/maestro.git
cd maestro
node install.mjsOr hand the repo to Claude Code and say: "run node install.mjs in this repo."
The installer validates options, settings, and every managed destination before changing anything. An ownership manifest records installed bytes; known-owned files update atomically, byte-identical reinstalls preserve file identity, and divergent or same-named user files cause a refusal instead of an overwrite. Ordinary late failures roll back every path published by that run; abrupt termination still leaves byte-atomic files that a rerun can reconcile. Settings/config backups refresh immediately before each merge, only a true top-level TOML web_search key satisfies the hang guard, disabled session prompts stay disabled on reinstall, and hook registrations carry exact Maestro markers rather than filename substring guesses.
Optional: the workflow rule
node install.mjs --with-workflowAdds workflow.md plus the ralph-protocol skill it defers to: a bounded execution loop on top of the orchestration loop. Plans live in tasks/todo.md, a verifier hierarchy decides what counts as proof, and long jobs run through /ralph-loop capped at 8 iterations with an explicit stop line. The Ralph loop drives the orchestration; the implementer inside it is still Codex.
node uninstall.mjsRemoves only files whose bytes still match the ownership manifest, strips only exact Maestro-marked commands from settings.json, and clears private direct-edit authorization markers. Modified hooks, rules, skills, foreign similarly named commands, and backups remain untouched; uninstall ownership does not depend on the current checkout version.
Stated plainly, because a tool that overstates its guarantees is worse than one that has fewer.
- The gate is a guardrail, not a boundary. It is registered for
Edit|Write|MultiEditonly.Bash, MCP tools, andWorkflow/Agentare not matched, so a redirect orsed -ireaches the tree untouched. Authorization requires a validated session, lives under a private~/.maestro/direct-editdirectory with owner/mode/content checks, ignores the legacy forgeable/tmpmarker path, and is revoked by malformed prompt payloads. Scratch/non-code exemptions use canonical existing targets or parents so symlinks cannot change classification; executable files remain gated regardless of extension. The orchestrator not writing source remains a discipline, not a sandbox. - Provenance detection reports, it never attributes. Each write-lease acquisition hashes actual materialized bytes with Git filters disabled, using Git itself rather than a platform-specific digest utility, across healthy worktrees, initialized submodules, and non-ignored nested repositories. One prunable worktree degrades independently. Baseline records publish before lease handoff, and log publication atomically replaces rather than follows a symlink. A mismatch names an interval—never a writer—and ignored paths remain out of scope on cost grounds.
MAESTRO_DIGEST_TIMEOUT_SECbounds each snapshot (default 120); timeout degrades that interval tounavailableand disables comparison rather than blocking dispatch. It is not an adversarial control. - Cancellation terminality is upstream. The companion does not expose the brokered turn's terminal event to Maestro's shell. A cancelled write may therefore leave unreported edits, so Maestro stops and retains the lease instead of guessing that the turn is quiescent.
- Same-vendor review. The orchestrator reviews its own plan's execution.
- Model pin depends on config being honored for debate max/ultra. Model and wrapper-supported efforts are explicit per task. Debate max/ultra rely on the top-level Codex config because the companion cannot express them; implementation therefore rejects max/ultra instead of silently substituting another tier. A fresh unpinned install cannot dispatch until model and effort values are selected.
- Plugin flag drift. Write dispatches preflight the companion's global
--helpand refuse only when it describestaskwithout--write; inconclusive help (empty, error, or no synopsis) proceeds rather than blocking work. - Windows. The watchdog is a bash script — run Claude Code from Git Bash or WSL.
- Codex on Plus. The implementer model is whatever your ChatGPT plan's Codex can reach.
MIT. See LICENSE. © elkaix / Pythoughts Labs.

