* fix(workspace-worktree): restore re-attaches existing branch instead of recreating with -b Restoring a session whose worktree directory was cleaned up but whose branch still existed locally would 422 with `fatal: a branch named <X> already exists`. The recovery path in `restore()` unconditionally fell through to `git worktree add -b`, even though `destroy()` deliberately preserves session branches so the user's commits aren't lost. When the local branch already exists, restore now clears any stale worktree registration at the target path and retries `git worktree add <path> <branch>` (no -b/-B). The existing -b fallback is preserved verbatim for the case where the local branch is genuinely missing (only the remote ref exists). -B is intentionally not used — it would force-reset the branch back to the base ref and silently discard the session's commits, which is the opposite of restore's intent. Test coverage: - 6 new unit tests covering the recovery path, cleanup tolerance, error propagation, and "no -b/-B" invariants - 2 updated existing unit tests (now mock the new refExists check) - 2 new integration tests exercising real git: branch preservation on clean restore, and recovery from a dirty teardown that left a stale registry entry Closes #1741 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(workspace-worktree): explicitly create main branch in restore integration tests CI runs git with a different `init.defaultBranch` than the local dev environment, so the bare clone has no `main` branch when the test attempts to push. Mirror the existing tests in this file (which also call `git switch -c <branch>` before the first commit). Fixes integration-test failures on PR #1742. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(workspace-worktree): rmSync stale workspace dir before retry Follow-up to the previous commit on this branch. The recovery path cleared the git worktree registry but didn't touch the filesystem, so when restore was triggered by a stale junk directory at the workspace path (workspace.exists() returns false because rev-parse fails on a non-working-tree dir), the retry would fail with the same error: fatal: '<workspacePath>' already exists Replace the inline `worktree prune` cleanup with a call to the existing `clearStaleWorktreePath()` helper, which handles all three states: - dir gone → no-op - dir present and not registered → rmSync - dir present and still registered after prune → throws (safety: never delete a registered worktree) This mirrors how create() already handles the same stale-state cases upfront via clearStaleWorktreePath at the top of its flow. Test coverage: - 1 new unit test: rmSyncs a stale workspace directory before retry - 1 new unit test: refuses to rmSync a still-registered worktree dir (data safety — error must propagate, not be swallowed) - 1 new integration test on real git: dir physically present as non-working-tree leftover, restore must rmSync it before retry, and the session commit must survive Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(workspace-worktree): extract restore helpers, drop redundant prune Addresses review feedback on PR #1742: 1. Extract two named helpers, reattachExistingBranch and createBranchFromBase, so restore()'s catch block reads as the bifurcation it actually is — 2 lines per branch, no nested try/catch hierarchy. Behavior is unchanged. 2. Drop the redundant `worktree prune` that ran inside the recovery path (via clearStaleWorktreePath). The entry-point prune in restore() is sufficient. reattachExistingBranch now inlines the existsSync + isRegisteredWorktree + rmSync sequence directly, keeping the data-safety guard ("refuse to rmSync a registered worktree") intact but skipping the second prune call. The catch block shrinks from ~46 lines to 12 (the rest moves into the helpers, where the docstrings can explain WHY each branch exists without cluttering the call site). Tests: same 64 unit + 13 integration tests still pass — the mock sequences in the recovery-path tests no longer expect a second prune call, since the new code doesn't make one. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(workspace-worktree): address Copilot review on PR #1742 Two real concerns flagged in inline review: 1. isRegisteredWorktree did strict string equality on paths. If `workspacePath` was passed in a non-canonical form (trailing slash, ".." segments) and git reported the canonical path, the check would false-negative — and the subsequent rmSync in cleanupStaleWorkspacePath would silently delete a still-registered worktree (data loss). Fix by resolve()-normalizing both sides before comparison. 2. createBranchFromBase (the "branch missing locally" recovery path) skipped the stale-path cleanup that reattachExistingBranch did. So if `workspacePath` had a stale dir AND the branch was missing, `git worktree add -b ...` would fail with the same "<path> already exists" error this PR was fixing for the re-attach case. Fix by factoring the cleanup into cleanupStaleWorkspacePath, called from both helpers. Test coverage: - Unit: path normalization safety — workspacePath with trailing slash vs canonical registered path must still throw "still registered" (proves rmSync is NOT called) - Unit: createBranchFromBase clears stale dir before -b add - Integration: branch missing locally + stale dir at workspacePath → restore must clean dir AND recreate branch from origin (commit preserved end-to-end on real git) Existing 2 "branch missing" tests updated to mock the new cleanup calls in createBranchFromBase. 66 unit tests + 14 integration tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .changeset | ||
| .cursor | ||
| .github | ||
| .husky | ||
| .issue-assets | ||
| artifacts | ||
| changelog | ||
| completions | ||
| docs | ||
| examples | ||
| handoff/pr-1466 | ||
| openclaw-plugin | ||
| packages | ||
| schema | ||
| scripts | ||
| skills | ||
| tests/integration | ||
| website | ||
| .eslintignore | ||
| .gitignore | ||
| .gitleaks.toml | ||
| .npmrc | ||
| .prettierignore | ||
| .prettierrc | ||
| AGENTS.md | ||
| ARCHITECTURE.md | ||
| CLAUDE.md | ||
| CONTRIBUTING.md | ||
| DESIGN.md | ||
| LICENSE | ||
| README.md | ||
| SECURITY.md | ||
| SETUP.md | ||
| TROUBLESHOOTING.md | ||
| agent-orchestrator.yaml.example | ||
| eslint.config.js | ||
| package.json | ||
| pnpm-lock.yaml | ||
| pnpm-workspace.yaml | ||
| tsconfig.base.json | ||
| tsconfig.node.json | ||
README.md
Agent Orchestrator — The Orchestration Layer for Parallel AI Agents
Spawn parallel AI coding agents, each in its own git worktree. Agents autonomously fix CI failures, address review comments, and open PRs — you supervise from one dashboard.
Agent Orchestrator manages fleets of AI coding agents working in parallel on your codebase. Each agent gets its own git worktree, its own branch, and its own PR. When CI fails, the agent fixes it. When reviewers leave comments, the agent addresses them. You only get pulled in when human judgment is needed.
Agent-agnostic (Claude Code, Codex, Aider) · Runtime-agnostic (tmux, ConPTY/process, Docker) · Tracker-agnostic (GitHub, Linear)
Quick Start
Prerequisites: Node.js 20+, Git 2.25+,
ghCLI, and:
- macOS / Linux: tmux — install via
brew install tmuxorsudo apt install tmux.- Windows: PowerShell 7+ recommended. tmux is not required — AO uses native ConPTY via the
runtime-processplugin (the default on Windows). SetAO_SHELL=bashif you have Git Bash and prefer it.
Install
npm install -g @aoagents/ao
Permission denied? Install from source?
If npm install -g fails with EACCES, prefix with sudo or fix your npm permissions.
To install from source (for contributors):
git clone https://github.com/ComposioHQ/agent-orchestrator.git
cd agent-orchestrator && bash scripts/setup.sh
Zsh Completion
Generate the completion file from the installed CLI:
mkdir -p ~/.zsh/completions
ao completion zsh > ~/.zsh/completions/_ao
Then make sure the directory is on your fpath before compinit runs:
fpath=(~/.zsh/completions $fpath)
autoload -Uz compinit
compinit
For Oh My Zsh, install the same generated file into a custom plugin directory and add ao to your plugin list:
mkdir -p "${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/ao"
ao completion zsh > "${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/ao/_ao"
If you are contributing from a source checkout, you can also symlink the repo copy at completions/_ao.
Start
Point it at any repo — it clones, configures, and launches the dashboard in one command:
ao start https://github.com/your-org/your-repo
Or from inside an existing local repo:
cd ~/your-project && ao start
That's it. The dashboard opens at http://localhost:3000 and the orchestrator agent starts managing your project.
Add more projects
ao start ~/path/to/another-repo
How It Works
- You start —
ao startlaunches the dashboard and an orchestrator agent - Orchestrator spawns workers — each issue gets its own agent in an isolated git worktree
- Agents work autonomously — they read code, write tests, create PRs
- Reactions handle feedback — CI failures and review comments are automatically routed back to the agent
- You review and merge — you only get pulled in when human judgment is needed
The orchestrator agent uses the AO CLI internally to manage sessions. You don't need to learn or use the CLI — the dashboard and orchestrator handle everything.
Configuration
ao start auto-generates agent-orchestrator.yaml with sensible defaults. You can edit it afterwards to customize behavior:
# agent-orchestrator.yaml
$schema: https://raw.githubusercontent.com/ComposioHQ/agent-orchestrator/main/schema/config.schema.json
# Runtime data is auto-derived under ~/.agent-orchestrator/{hash}-{projectId}/
port: 3000
defaults:
runtime: tmux # default on macOS / Linux; on Windows the default is `process` (ConPTY)
agent: claude-code
workspace: worktree
notifiers: [desktop]
projects:
my-app:
repo: owner/my-app
path: ~/my-app
defaultBranch: main
sessionPrefix: app
reactions:
ci-failed:
auto: true
action: send-to-agent
retries: 2
changes-requested:
auto: true
action: send-to-agent
escalateAfter: 30m
approved-and-green:
auto: false # flip to true for auto-merge
action: notify
CI fails → agent gets the logs and fixes it. Reviewer requests changes → agent addresses them. PR approved with green CI → you get a notification to merge.
Keep the $schema line so editors can autocomplete and validate against schema/config.schema.json.
See agent-orchestrator.yaml.example for the full reference, or run ao config-help for the complete schema.
Remote Access
AO keeps your Mac awake while running, so you can access the dashboard remotely (e.g., via Tailscale from your phone) without the machine going to sleep.
How it works: On macOS, AO automatically holds an idle-sleep prevention assertion using caffeinate. When AO exits, the assertion is released.
# agent-orchestrator.yaml
$schema: https://raw.githubusercontent.com/ComposioHQ/agent-orchestrator/main/schema/config.schema.json
power:
preventIdleSleep: true # Default on macOS; no-op on Linux and Windows
Set to false if you want to allow idle sleep while AO runs.
Lid-close limitation: macOS enforces lid-close sleep at the hardware level — no userspace assertion can override it. If you need remote access while traveling with the lid closed, use clamshell mode (external power + display + input device).
Linux / Windows: AO does not currently hold a wake assertion on these platforms. On Linux, idle-sleep behaviour is governed by your desktop environment / systemd-logind; configure that directly. On Windows, set the OS power plan if remote access matters while idle.
Plugin Architecture
Seven plugin slots. Lifecycle stays in core.
| Slot | Default | Alternatives |
|---|---|---|
| Runtime | tmux (macOS/Linux) / process (Windows) | process, docker |
| Agent | claude-code | codex, aider, cursor, opencode, kimicode |
| Workspace | worktree | clone |
| Tracker | github | linear, gitlab |
| SCM | github | gitlab |
| Notifier | desktop | slack, discord, composio, webhook, openclaw |
| Terminal | iterm2 | web |
All interfaces defined in packages/core/src/types.ts. A plugin implements one interface and exports a PluginModule. That's it.
Why Agent Orchestrator?
Running one AI agent in a terminal is easy. Running 30 across different issues, branches, and PRs is a coordination problem.
Without orchestration, you manually: create branches, start agents, check if they're stuck, read CI failures, forward review comments, track which PRs are ready to merge, clean up when done.
With Agent Orchestrator, you: ao start and walk away. The system handles isolation, feedback routing, and status tracking. You review PRs and make decisions — the rest is automated.
Documentation
| Doc | What it covers |
|---|---|
| Setup Guide | Detailed installation, configuration, and troubleshooting |
| CLI Reference | All ao commands (mostly used by the orchestrator agent) |
| Examples | Config templates (GitHub, Linear, multi-project, auto-merge) |
| Development Guide | Architecture, conventions, plugin pattern |
| Contributing | How to contribute, build plugins, PR process |
Development
pnpm install && pnpm build # Install and build all packages
pnpm test # Run tests (3,288 test cases)
pnpm dev # Start web dashboard dev server
See docs/DEVELOPMENT.md for code conventions and architecture details.
Contributing
Contributions welcome. The plugin system makes it straightforward to add support for new agents, runtimes, trackers, and notification channels. Every plugin is an implementation of a TypeScript interface — see CONTRIBUTING.md and the Development Guide for the pattern.
License
MIT