AgentOrchestration:用 PowerShell 编排并行 Claude Code 与 Copilot CLI 编码智能体
fuzz74/AgentOrchestration
AgentOrchestration 是一个 PowerShell 编排器,按依赖图并行运行 Claude Code 或 GitHub Copilot CLI 的无头编码智能体,每个智能体在独立 git worktree 中工作,只有通过测试和 review agent 的改动才会合并进 orch/integration。
Agent Orchestrator: Design & Usage
A small PowerShell harness that runs a dependency graph of coding tasks with parallel
Claude Code or GitHub Copilot CLI headless agents. Each agent works in its own git worktree,
and only work that passes tests and a review agent is merged. Requires PowerShell 7, git,
and the selected provider's CLI. Use -Provider Claude or -Provider Copilot on every
agent-running script; the same task plan works with either provider.
/write-spec ──► spec.md ──► Plan-Tasks.ps1 ─────────────────────► .orchestrator/tasks.json ──► (you review) ──► Invoke-Orchestrator.ps1 ──► orch/integration ──► (you merge) ──► Complete-Orchestrator.ps1
interview skeleton (new repo) + planner agent task graph parallel workers + gates archive the run
- At a glance
- From idea to finished application
- The task file
- Writing a spec
- Planning
- Scheduling
- Gates and merging
- Files and state on disk
- Command reference
- Agent permissions and safety
- Limits and known gaps
- Where the design comes from
At a glance
A planner agent turns a spec into tasks with dependencies. The scheduler runs every task whose dependencies are merged, several at once, each in a fresh worktree. A task is merged only after it passes its own checks, and each merge unblocks the tasks that depend on it.
┌──────────────────────────── scheduler loop (Invoke-Orchestrator.ps1) ───────────────────────────┐
│ ready = pending + all deps done + owns don't overlap a running task, up to -MaxParallel │
│ order: re-sync tasks first, then tasks with the most dependents (critical path) │
└───────────────┬──────────────────────────────────────────────┬───────────────────────────────────┘
│ one thread job per task │
▼ ▼
../<repo>.worktrees/<id> branch orch/task/<id>, cut from orch/integration (same for every other running task)
1 worker agent ──► 2 commit ──► 3 owns check ──► 4 merge orch/integration in ──► 5 acceptance ──► 6 review agent
▲ (resolver agent on conflict) command (2 verdicts)
│ │
└──────────── any gate fails: feedback goes back to the same worker session (--resume), up to maxAttempts ─┘
│ pass
▼
orch/integration ◄── merge --no-ff (single writer) ──► optional integrationCheck (merge undone if it fails) ──► dependents unblock
From idea to finished application
This walkthrough builds an application in a new repo, C:\src\myapp, with this repo
checked out at C:\Data\AgentOrchestration. Replace both paths with your own.
You do three things by hand: answer the spec interview, look over the plan and merge the result. Creating the repo, the project skeleton, the plan and the code is automated.
Where the work happens. Every step says which of these three places to use:
| Place | What it is | How to open it |
|---|---|---|
| Terminal | PowerShell 7 (pwsh), not Windows PowerShell 5.1 |
Windows Terminal with the PowerShell profile, or in VS Code Terminal → New Terminal. Check with $PSVersionTable.PSVersion (7.2 or later). |
| Agent chat | Claude Code or Copilot, interactive, started in the AgentOrchestration folder. Both can discover the shared /write-spec and /plan-tasks skills in .claude/skills. |
Open this folder in VS Code and use Claude Code or GitHub Copilot chat; or start claude or copilot in a terminal. |
| Editor | VS Code, for reading and editing files | File → Open File… or File → Open Folder… |
Orchestrator commands in the terminal steps assume the current folder is
C:\Data\AgentOrchestration.
Step 0: One-time setup
-
Install PowerShell 7.2+, git 2.20+, and the Claude Code or Copilot CLI you intend to use.
-
Log in to the selected CLI (
claudeorcopilot login). Scripts that run agents require-Providerand resolve-AgentPath, thenORCH_CLAUDE/ORCH_COPILOT, then the standalone executable on PATH, then the VS Code extension-managed install (Claude extension binary or Copilot CLI under VS Code globalStorage). You may install and use both at once. -
Get this repo (terminal):
git clone https://github.com/fuzz74/AgentOrchestration C:\Data\AgentOrchestration cd C:\Data\AgentOrchestration
-
Check that it works, without spending tokens (terminal): run
.\tests\Run-SmokeTest.ps1 -Provider Claudeand.\tests\Run-SmokeTest.ps1 -Provider Copilot. Each runs the whole flow with a fake provider, from a folder that doesn't exist yet to merged work. It covers:- creating the project skeleton, with one failed attempt that gets fixed
- a 4-task diamond
- a forced edit outside
owns, with a retry - a run with no structured result
- a merge conflict and a resolver run
- sub-agent calls by the planner and the workers, and their labels in the watch views
- finishing a run: an unfinished run blocks, a finished one is archived, and planning the next feature archives the earlier run by itself
It ends with
Smoke test passed. -
Access to your projects. The target repo is outside this folder. In Claude Code, Claude asks before reading or writing there. The first time you give
/write-specor/plan-tasksa repo path, Claude offers to allow that repo or its parent folder, such asC:\src. The parent folder covers all future projects. Say yes, then approve the edit Claude Code shows. It adds the folder to.claude\settings.local.json, which applies at once and is excluded by this repo's.gitignore. After that you aren't asked again.Claude can't grant itself access without that approval. To set access up yourself, add
{ "permissions": { "additionalDirectories": ["C:\\src"] } }to that file. In the CLI, you can also use/add-dir C:\src\myappor start withclaude --add-dir C:\src. In interactive Copilot CLI, grant access through its own permission flow or usecopilot --add-dir C:\src\myapp. The orchestration scripts run inside target repos and do not edit either assistant's interactive access settings.
Step 1: Turn the idea into a spec
Where: Agent chat in C:\Data\AgentOrchestration.
-
Type:
/write-specThen describe the idea in a few sentences and give the repo path
C:\src\myapp. Or all at once:/write-spec a habit tracker web app in C:\src\myapp. The folder doesn't need to exist yet. -
Answer its questions. It asks one at a time, mostly as multiple choice. It proposes 2-3 approaches, then walks through the design section by section and waits for your "yes" after each one:
- requirements
- modules and their file paths
- shared contracts (types, APIs, schema)
- build order
- verification commands
- constraints
For a new project, the stack and verification sections decide the project skeleton, so Claude pins them down: language, framework, test runner, and the setup and test commands.
-
It writes the spec to
C:\src\myapp\.orchestrator\spec.md. A reviewer subagent then checks it, and Claude fixes what the reviewer finds. -
Read the spec (editor): open
C:\src\myapp\.orchestrator\spec.md. Pay most attention to three sections:- 4.2 Modules: the paths become the task boundaries.
- 4.3 Shared contracts: everything parallel agents must agree on.
- 6 Verification: the setup and test commands, which every task relies on.
Ask for changes in the chat, or edit the file yourself. Tell the chat when you approve it.
The spec is pasted into every agent's prompt, so shorter and more precise is better. See Writing a spec.
Step 2: Plan the tasks
For a new project, planning first creates the project skeleton automatically:
git init- a bootstrap agent that sets up the manifest, dependencies, test runner and one smoke test for the spec's stack
- a commit
- a check, in a clean checkout, that the setup command and the whole-project check pass. If they fail, the agent gets the output and fixes the skeleton, up to 3 attempts.
The two commands are saved in .orchestrator/project.json and become the plan's setup and
integrationCheck. A repo that already has commits is left as it is.
Pick one:
- Interactive (agent chat, same conversation): accept the hand-off, or type
/plan-tasks. For a new project, Claude first runsInitialize-Project.ps1for you. Then it proposes the task table and lets you adjust it. Finally it writesC:\src\myapp\.orchestrator\tasks.jsonand validates it. - Scripted (terminal): one planner agent (Opus by default) plans without questions.
.\orchestrator\Plan-Tasks.ps1 -Provider Copilot -Spec C:\src\myapp\.orchestrator\spec.md -RepoPath C:\src\myapp
While it works, it prints one line per tool call of the skeleton and planner agents (for example[planner] Read src/app.csproj), so you can see it is making progress. Calls by the planner's sub-agents carry their description, for example[planner] ↳ [Survey the render code] Glob src/render/**. Planning a large spec takes several minutes. At the end it prints the planner's notes (assumptions, open questions) and the waves of parallel tasks. For an existing repo withoutproject.json, add-Setup 'npm ci'and-IntegrationCheck 'npm run build && npm test'(your own commands).
Step 3: Review the plan
Where: editor, then terminal.
-
Open
C:\src\myapp\.orchestrator\tasks.json. Check each task:ownsis tight, and tasks meant to run in parallel don't overlap.acceptanceis a real command that proves the task works.promptmakes sense on its own.settings.setupinstalls dependencies in a fresh checkout, andsettings.integrationCheckbuilds and runs all tests. After each merge the orchestrator runs both, in that order.
See The task file for every field.
-
Validate and preview the run with
.\orchestrator\Show-Tasks.ps1 -RepoPath C:\src\myappand.\orchestrator\Invoke-Orchestrator.ps1 -Provider Copilot -RepoPath C:\src\myapp -DryRun.-DryRunprints the waves and warns aboutownsoverlaps within a wave. Fix anything it reports, then run it again.
Step 4: Run the build
Where: terminal. Leave it open until the run ends.
.\orchestrator\Invoke-Orchestrator.ps1 -Provider Copilot -RepoPath C:\src\myapp -MaxParallel 3
-
Progress prints live. For a live dashboard, open a second terminal in
C:\Data\AgentOrchestrationand run:.\orchestrator\Watch-Orchestrator.ps1 -Provider Copilot -RepoPath C:\src\myapp
It redraws every 2 seconds and shows:
- a progress bar, task counts and cost so far
- how many agents are working, and on which tasks
- each running agent's phase and its latest tool calls; lines from its sub-agents start
with
↳ [description] - live headless Claude or Copilot CLI process IDs on Windows, including on a running task
line when the CLI's
orch:<task-id>name matches; the process list is system-wide and names are not verified against this repository (resumed Copilot sessions may lack a name) - the latest rejected review's verdict, summary and issues for running or failed tasks; on short terminals it shows one issue and a count of the rest in the saved review log
- every task's status
- the last lines of the log
To follow the agents' requests, responses and review feedback instead of the dashboard, open another terminal and run:
.\orchestrator\Watch-Conversations.ps1 -RepoPath C:\src\myapp
Requests show the first 12 lines and a clickable Open control for the full prompt; the latest request also has a pinned Open full request link at the top of the live view. An agent's requests to its sub-agents, and the sub-agents' messages, are labeled with the agent and the sub-agent's description, for example
[contracts/worker#1 ↳ Survey billing module]. Request and response headings are not links. The full prompt opens in a scrollable in-terminal popup. Use the wheel, the popup scrollbar (click or drag), or arrow/PgUp/PgDn keys to scroll; Esc closes the popup; its [X] control is also clickable.-NoMousepreserves normal terminal selection, with O opening the latest request instead. Use-FullRequeststo print every line in the transcript, or-Task terminalto focus on one task,-ShowToolsto include tool calls, or-Onceto print recent messages and exit. Ctrl+C stops the watcher, not the run.To see how the orchestrator and agents work together, use the read-only teaching timeline:
.\orchestrator\Watch-AgentTimeline.ps1 -RepoPath C:\src\myapp
It combines run decisions, saved prompts, model calls where logged, agent commentary, tool starts/completions and review results; entries from sub-agents start with
↳ [description]. Use-Task runtime-processto focus on one task or-Onceto print a snapshot; arrows, PgUp/PgDn, Home/End and Q control the live view. Tool output and private reasoning are not shown. The original conversation watcher remains the place to read full agent responses and request text.Press Ctrl+C to close it; the run keeps going. It also works during
Plan-Tasks.ps1, where it shows the skeleton or planner agent. For a one-off status table instead, run.\orchestrator\Show-Tasks.ps1 -RepoPath C:\src\myapp. You can also openC:\src\myapp\.orchestrator\progress.mdin the editor. While an agent works, a heartbeat line at most once a minute shows how many tool calls it has made and the latest one, for example[s2] worker: 14 tool calls, last: Edit src/Game.cs. -
Agents work in
C:\src\myapp.worktrees\<task-id>. Don't edit files inC:\src\myappor in those folders during the run. -
The exit code is 0 when every task is done and 2 when some failed or were blocked.
-
To stop after active agent sessions finish, run this in a second terminal:
.\orchestrator\Request-OrchestratorStop.ps1 -RepoPath C:\src\myapp
The runner finishes active sessions, saves their work in the task worktrees, and does not start another agent session or task. It clears the request when it exits. Rerun the same
Invoke-Orchestrator.ps1command without-RetryFailedto resume paused tasks and their review/acceptance checks; previously failed tasks remain failed. If you requested a stop when no runner was active or changed your mind, clear the request withRequest-OrchestratorStop.ps1 -RepoPath C:\src\myapp -Cancel. A runner already started before this feature was installed cannot observe the new stop request. -
Ctrl+C interrupts the runner immediately. Running agent processes can keep going for a while; check Task Manager. Prefer the stop request when you need to retain current work.
Step 5: Fix failed tasks
Skip this step if every task is done.
- Find out why (terminal): run
.\orchestrator\Show-Tasks.ps1 -RepoPath C:\src\myapp. The detail column shows the error. For the full story, open the task's latest log folder,C:\src\myapp\.orchestrator\logs\<task-id>\<timestamp>\, in the editor. It holds the prompts, the agent results and the command output. Each*.events.jsonlfile is the agent's full event stream, one JSON event per line: every message and tool call. - Fix the cause (editor):
- Usually edit the task in
tasks.json: a clearerprompt, widerowns, or a correctacceptancecommand. - If the spec itself was wrong, fix
spec.mdas well. - If the work is too big for one task, add a task. Adding tasks between runs is fine.
- Usually edit the task in
- Retry (terminal): run
.\orchestrator\Invoke-Orchestrator.ps1 -Provider Copilot -RepoPath C:\src\myapp -RetryFailed. A clean failed task branch with committed changes is preserved and synchronized with newer dependencies before its checks and reviewer run again. A dirty failed worktree stops the retry so uncommitted work is not lost. To deliberately start failed tasks over, add-FreshFailedto-RetryFailed: each existing task branch is archived underorch/archive/<task-id>/<timestamp>before its worktree is recreated. Review the archived branch if you need to recover the earlier attempt.
Step 6: Try the finished application
Where: editor and terminal.
The result is on the orch/integration branch, which is checked out in
C:\src\myapp.worktrees\_integration.
- Read the changes. In VS Code, File → Open Folder… →
C:\src\myapp.worktrees\_integration. Or list them in a terminal:git -C C:\src\myapp diff --stat main...orch/integration
- Run the app and its tests in a terminal in that folder:
cd C:\src\myapp.worktrees\_integration npm ci npm test npm start
- Small fixes: make them yourself, or with Claude chat opened in that folder, and commit
them on
orch/integration. Bigger gaps: add tasks totasks.jsonand go back to step 4.
Step 7: Merge and finish the run
Where: terminal.
-
Merge into your base branch. The main checkout must be on
mainwith no uncommitted changes.git -C C:\src\myapp switch main git -C C:\src\myapp merge --no-ff orch/integration
-
Push if the repo has a remote:
git -C C:\src\myapp push. The orchestrator never pushes. -
Finish the run (run from
C:\Data\AgentOrchestration):.\orchestrator\Complete-Orchestrator.ps1 -RepoPath C:\src\myapp. It moves the run record (plan, spec, state, progress log and logs) toC:\src\myapp.runs\<timestamp>\.orchestratorand removes the worktrees and theorch/*branches. Onlyproject.jsonstays in.orchestrator.- It refuses while a run is active, and it refuses a run that is not finished: a task
that is not done, commits on
orch/integrationthatmainlacks, or uncommitted changes in a worktree.-Forcearchives such a run as it is. The uncommitted changes are then deleted, and the removed branches' commits are no longer on any branch;branches.txtin the archive lists them. -Keep <file>leaves a file in.orchestrator, for example the spec of the next feature. Add-WhatIffirst if you want to see what it would do.- First close any VS Code window or terminal that is open in a worktree folder: Windows can't delete a folder that is in use.
You can skip this step: planning the next feature does the same by itself.
- It refuses while a run is active, and it refuses a run that is not finished: a task
that is not done, commits on
Step 8: The next feature
Go back to step 1 with the next idea. /write-spec now sees the code that exists and
specifies only the change. If the earlier run is still in .orchestrator, it saves the new
spec next to the old one under a new name, such as .orchestrator\spec-<topic>.md. Then:
- Plan as before, with
Plan-Tasks.ps1or/plan-tasks. No-Forceis needed. If the earlier run is still in.orchestrator, planning first archives it the wayComplete-Orchestrator.ps1does, and leaves the spec you pass in place. If that run is not finished, planning stops and says why;-Forcearchives it anyway. A plan that never ran is not archived: planning stops, or overwrites it with-Force. - The repo now has commits, so no new skeleton is made, and
project.jsonstill supplies the commands. .orchestrator/is never committed, and the archive inC:\src\myapp.runslies outside the repo. To keep old specs in the repo, copy them to a folder such asC:\src\myapp\docs\specs\and commit them.
The task file
.orchestrator/tasks.json is the whole plan. You own it: edit it freely between runs.
Run state lives in a separate file (state.json), keyed by task id, so editing the plan
never loses progress. The JSON Schema is
orchestrator/schemas/tasks.schema.json.
{
"version": 1,
"spec": ".orchestrator/spec.md",
"baseBranch": "main",
"integrationBranch": "orch/integration",
"settings": { "model": "sonnet", "reviewModel": "sonnet", "maxAttempts": 3, "setup": "npm ci",
"shared": ["package-lock.json"] },
"tasks": [
{ "id": "api-types", "title": "Shared API types", "deps": [], "owns": ["src/types/**"],
"acceptance": "npx tsc --noEmit", "prompt": "Define the request and response types for ..." },
{ "id": "users-api", "title": "Users endpoints", "deps": ["api-types"], "owns": ["src/users/**"],
"acceptance": "npm test -- src/users", "prompt": "Implement GET/POST /users using the types in src/types ..." },
{ "id": "orders-api", "title": "Orders endpoints", "deps": ["api-types"], "owns": ["src/orders/**"],
"acceptance": "npm test -- src/orders", "prompt": "..." }
]
}Task fields
| Field | Required | Meaning |
|---|---|---|
id |
yes | Unique, ^[a-z0-9][a-z0-9._-]{0,48}$. Becomes branch orch/task/<id> and folder names. |
title |
yes | One line, used in logs and commit messages. |
prompt |
yes | Self-contained instructions for the worker. |
deps |
no | Ids that must be merged before this task starts. |
owns |
no | Repo-relative globs the task may edit (**, *, ?; a plain path covers a file or a folder). Tasks with overlapping owns never run together. Empty = whole repo, so the task runs alone. |
acceptance |
no | Command run with pwsh in the worktree root. Exit code 0 = pass. Write it as plain PowerShell; do not wrap it in pwsh -Command "..." (see Limits and known gaps). |
model |
no | Worker model for this task only (Claude; Copilot is pinned to GPT-6 Sol). |
Settings (all optional; the defaults are in Orchestrator.psm1)
| Setting | Default | Meaning |
|---|---|---|
model |
sonnet |
Worker model (alias or full id). |
reviewModel |
sonnet |
Model for the reviewer and the conflict resolver. |
effort |
none | --effort for workers: low … max. |
permissionMode |
acceptEdits |
Worker permission mode: acceptEdits, auto, dontAsk, bypassPermissions. |
allowedTools |
Read, Edit, Write, Glob, Grep, Bash, PowerShell |
Tools the worker may use without a prompt, in permission-rule syntax (e.g. Bash(npm *)). |
maxBudgetUsd |
0 |
--max-budget-usd for Claude calls; 0 means no cap. Copilot does not support this USD limit. |
maxAttempts |
3 |
Limit for worker, ownership, merge-sync and acceptance failures. Review-only spec failures allow five rejections; quality-only failures have no count limit. |
review |
true |
Run the review agent after acceptance passes. |
setup |
none | Command run once in each fresh worktree (e.g. npm ci, uv sync). |
integrationCheck |
none | Command run in the integration worktree after each merge, after setup. A failure undoes the merge and fails the task. |
commandTimeoutSec |
1800 |
Timeout for setup, acceptance and integration commands. |
enforceOwns |
true |
Reject a task that edits files outside owns + shared. |
shared |
[] |
Globs any task may edit (lock files, registries). Not used for scheduling, so edits to them can conflict. The resolver handles those conflicts. Plan-Tasks.ps1 fills it from the planner's shared list. |
additionalDirectories |
[] |
Existing folders agents must access outside their worktree, e.g. "additionalDirectories": ["C:\\src\\reference", "../shared-source"]. Relative paths resolve from the project root; missing folders fail plan validation. Passed as --add-dir to worker, reviewer and resolver CLI sessions (including retries). Grants file access, not read-only enforcement; only list trusted folders and do not rely on owns to protect files outside the worktree. |
ignore |
__pycache__, *.pyc, .pytest_cache, .mypy_cache, .venv, node_modules, .DS_Store |
Globs never committed from a worktree, even when the repo's .gitignore misses them. Setting this replaces the whole default list. |
Writing a spec
The planner, every worker and every reviewer get the whole spec in their prompt, and none of them can ask questions. A spec that splits well is short, and it names things precisely. It gives:
- module boundaries as file paths (they become
owns) - shared interfaces written out as code (they become the contracts-first task)
- a build order (it becomes
deps) - runnable test commands (they become
acceptance) - testable requirements with ids that task prompts and reviewers can cite
The /write-spec skill (.claude/skills/write-spec)
writes such a spec with you:
- It interviews you one question at a time, proposes approaches and gets each design section approved.
- It writes the spec from a template, with EARS acceptance criteria.
- It has the spec checked by a fresh reviewer subagent.
- It saves the result to
.orchestrator/spec.mdand hands off to/plan-tasks.
The method adapts Superpowers' brainstorming skill and cc-sdd's requirements and design rules. Copies of the originals and their MIT licenses are in references.
Planning
If the target repo is new (no folder, no git repo, or no commits), Plan-Tasks.ps1 first
calls Initialize-Project.ps1:
git init -b main, then a bootstrap agent (prompts/bootstrap.md, the worker model and tools) creates the skeleton for the stack in the spec: manifest, lock file, test runner,.gitignore, one smoke test and a README. It writes no feature code and nothing inside module paths.- The script commits the skeleton, then runs the agent's setup command and whole-project check in a clean detached worktree. A failure goes back to the same session, and the fix is amended into the one commit, up to 3 attempts.
- The two commands go to
.orchestrator/project.json. They become the default-Setupand-IntegrationCheck.
Then Plan-Tasks.ps1 runs one planner agent (--model opus by default) in the target repo.
The planner has only Read, Glob and Grep, plus sub-agents with the same tools, so it
can explore the code but not change it. Its prompt is
prompts/planner.md. The output must match
plan-output.schema.json (--json-schema).
Before the planner runs, Plan-Tasks.ps1 looks for an earlier run (state.json) in
.orchestrator. A finished one is archived to <repo>.runs (see
Files and state on disk); an unfinished one stops the script
unless -Force. This way a new plan never sits next to old state, where a task that reuses
an old id would count as done.
The script then:
- Copies the spec to
.orchestrator/spec.mdif the spec lives outside the repo. - Writes
tasks.jsonwith the planner's tasks, its shared files (settings.shared) and your settings (-WorkerModel,-Setup,-IntegrationCheck). - Validates the graph: schema, ids, unknown deps, self-deps, cycles. If the graph is invalid, the planner session is resumed once with the list of problems.
- Prints the planner's notes (assumptions and open questions) and the waves (
-DryRun).
The planner prompt asks for right-sized tasks (15–60 minutes of agent work), few
dependencies, a contracts task first when parts must share an interface, tight
non-overlapping owns, a targeted acceptance command per task, and self-contained
prompts. The /plan-tasks skill (.claude/skills/plan-tasks)
applies the same rules interactively, so you can discuss the split before the file is written.
Scheduling
The scheduler is a single loop in Invoke-Orchestrator.ps1. It is the only writer of
state.json and of the integration branch.
- Ready: status
pendingand every dependencydone(merged). - Isolation: a ready task waits if its
ownsoverlaps a running task'sowns. The overlap test is conservative: two globs overlap when the literal prefix of one (the part before the first wildcard) starts with the other's prefix. - Order: tasks re-queued for a sync go first. After them, tasks with the most transitive dependents go first, so the critical path starts early.
- Parallelism: up to
-MaxParallel(default 3) tasks run as PowerShell thread jobs. Each job runs the whole per-task pipeline and returns a verdict. - Worktrees:
../<repo>.worktrees/<id>on branchorch/task/<id>, cut from the current tip oforch/integration. So a task always starts from its merged dependencies. Worktrees sit outside the repo folder so agents never see or edit the main checkout. - Context between tasks: when a task finishes, the worker returns a
summaryandnotes_for_dependents. Both are pasted into the prompts of the tasks that depend on it, and the code itself is on the branch those tasks start from.
Gates and merging
Each task runs this pipeline in its worktree (see Invoke-TaskPipeline in
Orchestrator.psm1):
- Setup (fresh worktrees only): the
setupcommand, if set. - Worker:
claude -pwith prompts/worker.md and worker-result.schema.json. The worker returnsdoneorblocked, a summary, and notes for dependents.blockedfails the task at once. Models sometimes finish the work but skip the structured result, or only claim they returned it. In that case the session is resumed once with "report your result now". If there is still no result, the worker's plain-text answer is used as the summary, and the gates below decide. - Commit: the orchestrator commits everything in the worktree except the
ignoreglobs (build artifacts such as__pycache__). Workers are told not to commit. - Ownership: files changed against
orch/integration(merge-base diff) must matchowns+shared, and there must be at least one change. - Sync:
orch/integrationis merged into the task branch. If that conflicts, a resolver agent (prompts/resolver.md) resolves the conflicts and stages the result. Leftover conflict markers abort the merge and count as a failure. - Acceptance: the task's command, with a timeout. The last 80 lines of output become feedback on failure.
- Review: a read-only reviewer (prompts/reviewer.md)
gets the task, the spec and the diff. It returns a spec-compliance verdict and a
code-quality verdict. Either
failrejects the change, and the reviewer's issues become feedback.
When a gate fails, the same worker session is resumed (--resume) with the feedback
(prompts/retry.md). Non-review failures stop after
maxAttempts attempts. Reviewer spec failures stop after five spec rejections (including
mixed spec/quality failures); quality-only rejections retry without a count limit, until
review passes, another kind of failure occurs, or the run is interrupted. Unlimited
retries can incur ongoing model costs; Copilot has no USD budget cap.
When all gates pass, the scheduler merges orch/task/<id> into orch/integration with
--no-ff. If that merge conflicts (another task merged in the meantime), the task is
re-queued in sync mode. A sync run repeats steps 5–7 in the same worktree without calling
the worker. After more than 3 conflicting merges, the task fails. If integrationCheck is
set, it runs after each merge, after setup in the integration worktree, so that
dependencies are installed. The merge and the check run on a detached HEAD, and the
integration branch moves to the merge only when the check passes. Tasks that sync while
the check runs therefore never pick up a merge that is then undone. A failing setup or
check leaves the branch where it was and fails the task.
Statuses: pending → running → done or failed. A pending task with a failed task
upstream is shown as blocked.
Files and state on disk
| Path | What it holds |
|---|---|
<repo>/.orchestrator/tasks.json |
The plan. Yours to edit. |
<repo>/.orchestrator/spec.md |
The spec: written there by /write-spec, or copied there when it lives outside the repo. |
<repo>/.orchestrator/project.json |
Setup and integration-check commands from the skeleton bootstrap. |
<repo>/.orchestrator/state.json |
Per-task status, attempts, cost, session id, summary, notes, error, merged commit. |
<repo>/.orchestrator/progress.md |
Timestamped log of the whole run. |
<repo>/.orchestrator/logs/<id>/<timestamp>/ |
Per-run logs: each prompt sent (*.prompt.md), each claude JSON result, and command output. |
../<repo>.worktrees/<id> |
Task worktree on orch/task/<id>. |
../<repo>.worktrees/_integration |
Worktree holding orch/integration. |
../<repo>.runs/<timestamp>/.orchestrator/ |
A finished run, archived at that local time: everything above from .orchestrator, a copy of project.json, and branches.txt. |
.orchestrator/ is added to .git/info/exclude, so it is never committed and never
appears in the worktrees.
Finishing: Complete-Orchestrator.ps1, or Plan-Tasks.ps1 before it plans, moves every
entry in .orchestrator except project.json (and files it is told to keep) to the
archive, and removes the worktrees and the orch/* branches. A run is finished when every
task in tasks.json is done in state.json, the integration branch is gone or has no
commit that the base branch lacks, and no worktree has uncommitted changes (changed or
new files, apart from git-ignored ones and the plan's ignore globs). An archived run has the same layout as a live one.
branches.txt lists each removed branch with its last commit, so
git branch <name> <commit> brings one back as long as git still has the commit.
Restarting: rerun Invoke-Orchestrator.ps1. Done tasks stay done. Tasks left
running by an interrupted run start again from scratch. Failed tasks stay failed until
you pass -RetryFailed, usually after you edit their prompt or owns. You can add tasks
to the plan between runs. A graceful stop instead records active tasks as pending with
their worktrees and retry feedback intact; rerun without -RetryFailed to continue them.
If a task fails after its work was committed (for example, its reviewer could not launch),
-RetryFailed removes and recreates its worktree and branch. To keep that commit, first
verify that its worktree is clean and the worker is no longer running. Then set that task's
status to pending and mode to sync in state.json and rerun without
-RetryFailed. Sync mode reuses the worktree, skips the worker, and reruns ownership,
acceptance, and review checks before merging. Do not use sync mode for a task whose
implementation or acceptance check needs repair.
Command reference
| Script | Parameters |
|---|---|
Initialize-Project.ps1 |
`-Provider Claude |
Plan-Tasks.ps1 |
`-Provider Claude |
Invoke-Orchestrator.ps1 |
`-Provider Claude |
Request-OrchestratorStop.ps1 |
-RepoPath (.), -Cancel (remove a pending stop request). Graceful stop exits the runner with code 0 even if tasks are still pending. |
Show-Tasks.ps1 |
-RepoPath, -Plan. Validates the plan and prints wave, status, deps, attempts, cost and detail per task. |
Watch-Orchestrator.ps1 |
`-Provider Claude |
Watch-Conversations.ps1 |
-RepoPath (.), -Task (optional task ID), -Last (25), -RefreshSeconds (2), -ShowTools, -FullRequests, -NoMouse, -Once. Live view has clickable request controls and a scrollable in-terminal popup (O opens the latest request without mouse); -Once prints plain output. Ctrl+C or Q quits. |
Clear-Orchestrator.ps1 |
-RepoPath, -All. Without -All: removes task worktrees and orch/task/* branches, resets unfinished tasks to pending. With -All: also the integration worktree and branch, state, logs and progress, which are deleted, not archived. Refuses while a run is active. Supports -WhatIf. |
Complete-Orchestrator.ps1 |
-RepoPath, -Keep (files to leave in .orchestrator, by name or full path), -Force (archive a run that is not finished). Moves the run record to <repo>.runs/<timestamp>/.orchestrator, leaves project.json, and removes all worktrees and orch/* branches. Refuses while a run is active. Supports -WhatIf. |
tests/Run-SmokeTest.ps1 |
`-Provider Claude |
Agent permissions and safety
| Agent | Tools | Permission mode |
|---|---|---|
| Bootstrap | worker defaults | acceptEdits |
| Planner | Read, Glob, Grep and Task (sub-agents) only (--tools) |
dontAsk |
| Worker | allowedTools setting, plus Task (sub-agents) |
permissionMode setting (default acceptEdits) |
| Resolver | worker tools + git add/status/diff |
acceptEdits |
| Reviewer | Read, Glob, Grep only |
dontAsk |
Claude calls use --permission-prompts none, plus --max-budget-usd when a cap is set. They also
pass --settings '{"autoMemoryEnabled":false}', so agents never read or write Claude Code's
per-machine auto memory, and --disallowedTools with the claude.ai Spotify, Strava and Claude Docs
connectors, so agents never see those tools (the list is $script:ClaudeDeniedMcpServers in
Orchestrator.psm1; interactive sessions keep them). Copilot calls use
--allow-all-tools with --available-tools mapped from the requested tools (Read to
view, Glob to glob, Grep to rg, Edit and Write to apply_patch, Bash and
PowerShell to powershell and its read_, stop_ and list_powershell companions,
Task to task, read_agent and list_agents), so that filter is what keeps Copilot's
planner and reviewer read-only; its
CLI has no equivalent to Claude's USD cap or command-scoped Bash(...) rules (those rules
are rejected for Copilot). Copilot's JSON results are checked against the local schemas;
Claude uses its native --json-schema output. Every Copilot run (bootstrap, planner,
worker, reviewer, and resolver) uses --model gpt-6-sol, including when command-line
model arguments or an existing plan specify another model. Claude retains its model settings.
- Workers run with
Bash/PowerShellallowed by default, so they can run any command as you. Run the orchestrator only on repos you trust, or narrowallowedTools(for exampleBash(npm *)) or usepermissionMode: auto. - Workers and the planner can hand independent questions that need a lot of reading to
sub-agents (prompts/subagents.md), started in the
foreground. A sub-agent has no more tools than its agent:
dontAskbinds Claude's sub-agents too, and a Copilot sub-agent gets its agent's--available-tools(tested; see docs/copilot-cli-notes.md). So the planner's sub-agents can only read. A worker's could edit files, so its prompt says to tell them not to. The reviewer (one small diff per task, where sub-agents would only add cost), the bootstrap and the resolver (small, focused jobs) are not given sub-agents, though Claude sessions without--tools(bootstrap, resolver) still see the tool (docs/claude-cli-notes.md). claude -ploads the project'sCLAUDE.md, hooks, skills and.mcp.jsonin every worktree and shows no trust prompt. That is useful for conventions, and a reason to run only trusted repos.- Nothing is pushed. All merges are local, and merging
orch/integrationinto your base branch is left to you.
Limits and known gaps
- Interrupting: Ctrl+C stops the thread jobs, but agent processes already running can keep going until they finish. Check Task Manager. Their tasks restart from scratch on the next run.
- Shared machine resources: parallel tasks share ports, databases and caches. Tests
that bind fixed ports can collide. Lower
-MaxParallelor make tests use random ports. - Acceptance exit codes: the command runs as
pwsh -EncodedCommand. Chain steps with&&so an early failure is not hidden by a later success. - Acceptance quoting: the command is already PowerShell. A nested
pwsh -Command "$t = ...; ..."fails every time, because the outer pwsh expands$tto an empty string inside the double quotes before the inner one starts. Write the script directly:$t = Get-Content README.md -Raw; if ($t -match 'foo') { exit 0 } else { exit 1 }. After&&or||, PowerShell accepts only a command, not an assignment, so./publish.ps1 && $t = ...does not parse at all. Write./publish.ps1; if (-not $?) { exit 1 }; $t = .... Plan validation (Test-Plan, run byPlan-Tasks.ps1,Show-Tasks.ps1andInvoke-Orchestrator.ps1) parses every acceptance,setupandintegrationCheckcommand and rejects the plan if one does not parse. - Ownership is path-based:
ownsprevents two agents from editing the same files at once. It does not stop semantic conflicts, for example two tasks that each change behaviour the other relies on.integrationCheckand good contracts tasks are the defence. - One integration branch that is never rebased onto a moving base branch. Merge or
rebase it yourself if
mainmoves during a long run. - No agent-to-agent messaging: tasks share context only through code, summaries and
notes. That is deliberate, because it keeps every worker's context small. Within its own
session, a worker or the planner can hand independent questions to sub-agents and work
from their summaries. The prompts don't ask for follow-up messages to sub-agents, and
Copilot's
write_agent, which sends them, is not given. - Cost: each task costs at least one worker call and one review call. Set
review: falsefor trivial tasks or use a cheaperreviewModel. Sub-agents add to a worker's or the planner's cost, because each starts with a fresh context.state.jsonrecords Claude's client-side USD estimate, sub-agents included; Copilot does not provide this value (shown as zero). - Large diffs: the reviewer gets the list of changed files, but at most 80,000 characters of the diff; for the rest it has to read the files. Prompts reach both CLIs on stdin, so their size is not limited by the Windows command line.
- Model choice: Haiku workers do fine on small, well-specified tasks. They are more likely to skip the structured result, and the nudge covers that. Use Sonnet or better for real work and for the reviewer.
- Tested: on Windows 11 with PowerShell 7.6, git 2.49 and Claude Code 2.1.283. Tests: the smoke test, plus a real run. In the real run, a Sonnet planner split a 3-part Python spec into a chain of 3 tasks, and Haiku workers and reviewers built it. All 3 passed on the first attempt: 24 tests, 0.50 USD for the build, 0.27 USD for planning. The paths are cross-platform, but macOS and Linux are untested.
Where the design comes from
| Choice here | Borrowed from |
|---|---|
| Stateless workers in a script loop; state kept in files and git, not in agent context | Building a C compiler with a team of parallel Claudes, Ralph |
| A planner writes a structured task list up front; one task per session; progress log | Effective harnesses for long-running agents |
| Fresh worker per task and two review verdicts (spec, quality) before accepting | Superpowers: subagent-driven-development |
| Explicit dependency graph; only "ready" tasks can be claimed | Beads, Claude Code agent teams task list |
| One worktree per agent; merge back after validation | Claude Code: run agents in parallel, The Code Agent Orchestra |
| Orchestrator-workers split: one lead plans, workers get isolated context | Orchestrator-workers cookbook, How we built our multi-agent research system |
| Spec by interview; EARS criteria; file-structure plan and contracts drive the task split | Superpowers: brainstorming, cc-sdd, Kiro specs |
claude -p with --json-schema, --resume, --permission-prompts none |
Run Claude Code programmatically |
Short, grep-friendly test output and deterministic gates matter more than clever prompts. That is the main lesson of the C-compiler write-up, and it is why acceptance feedback is trimmed to the last 80 lines.
来源:GitHub · github.com