* fix(lifecycle): reduce GitHub API rate limiting from batch enrichment bypass
Three optimizations to prevent API storms in the lifecycle manager poll cycle:
1. **CRITICAL - maybeDispatchMergeConflicts**: Gate the getMergeability()
fallback to only run when batch enrichment didn't run at all. Previously
it called getMergeability() (3 REST calls) whenever hasConflicts was
undefined, even when the batch had already fetched PR data. Now uses
cachedData.hasConflicts ?? false when the batch ran.
2. **HIGH - maybeDispatchCIFailureDetails**: Use batch enrichment ciChecks
when available instead of calling getCIChecks() (separate REST call)
on every poll. The GraphQL batch query now fetches statusCheckRollup
contexts (individual check names, statuses, URLs) alongside the rollup
state. Falls back to getCIChecks() only when batch didn't run.
3. **MEDIUM - maybeDispatchReviewBacklog**: Throttle getPendingComments +
getAutomatedComments API calls to at most once per 2 minutes per session.
These were called every 30s even when nothing had changed.
Impact: ~8-10 API calls/PR/poll reduced to ~2-4, enabling 3-4x more
concurrent sessions before hitting GitHub's 5,000/hr REST limit.
Also extends PREnrichmentData with ciChecks?: CICheck[] and adds
parseCheckContexts() helper to graphql-batch.ts for parsing CheckRun
and StatusContext nodes from the GraphQL statusCheckRollup.contexts field.
* fix(scm-github): fall back to getCIChecks() when contexts list is truncated
When a PR has >20 CI checks, contexts(first: 20) silently truncates the
list. Setting ciChecks to undefined when pageInfo.hasNextPage is true
ensures maybeDispatchCIFailureDetails falls back to the getCIChecks()
REST call, which returns all checks without truncation.
Also adds pageInfo { hasNextPage } to the contexts GraphQL query so
truncation can be detected.
* fix(lifecycle): prune lastReviewBacklogCheckAt in pollAll cleanup loop
Add the new throttle map to the existing pruning loop that removes stale
entries for sessions no longer in the session list. Previously the map
was only cleared on terminal status transitions, leaving orphaned entries
for sessions removed externally (killed + cleaned up without transition).
* fix(lifecycle): bypass throttle on review transition; fix StatusContext conclusion
Two fixes for automated review findings:
1. Bypass review backlog throttle when a transition reaction just fired for
humanReactionKey or automatedReactionKey. The transitionReaction branch
needs to read the current fingerprint via the API to record
lastPendingReviewDispatchHash. Without bypassing, the throttle prevents
this write and the next unthrottled poll sees a stale (empty) hash,
clears the reaction tracker, and fires a duplicate dispatch.
2. Set conclusion on StatusContext nodes in parseCheckContexts() to match
the REST getCIChecksFromStatusRollup() format (rawState.toUpperCase()).
The CI failure fingerprint includes c.conclusion ?? '', so inconsistent
conclusion values between GraphQL and REST paths caused phantom fingerprint
changes when switching sources, triggering duplicate dispatches.
* fix(scm-github): normalize CheckRun conclusion and map NEUTRAL to skipped
Two consistency fixes in parseCheckContexts() vs the REST path:
1. NEUTRAL conclusion: was mapped to 'passed' (with SUCCESS), but
mapRawCheckStateToStatus() in the REST path maps NEUTRAL to 'skipped'.
Changed to treat NEUTRAL the same as SKIPPED.
2. CheckRun conclusion: was stored as the raw GraphQL string (may be
lowercase). REST getCIChecks/getCIChecksFromStatusRollup always store
conclusion as rawState.toUpperCase(). Now stores rawConclusion which
is already uppercased during the status branching logic.
Both fixes prevent phantom fingerprint changes when maybeDispatchCIFailureDetails
switches between GraphQL batch and REST fallback across poll cycles.
* fix(scm-github): map STALE/NOT_REQUIRED/NONE conclusions to skipped
parseCheckContexts() was mapping these conclusions to 'failed' via the
else fallback, while mapRawCheckStateToStatus() in the REST path
explicitly maps all of them to 'skipped'. Added them to the skipped
branch alongside SKIPPED and NEUTRAL to fully mirror the REST mapping.
* fix(scm-github): map QUEUED/WAITING to pending not running
parseCheckContexts() mapped QUEUED and WAITING CheckRun statuses to
'running', but mapRawCheckStateToStatus() in the REST path maps both
to 'pending'. Only IN_PROGRESS maps to 'running' in the REST path.
Fixes fingerprint inconsistency when switching between GraphQL batch
and REST fallback across poll cycles.
* fix(scm-github): map STARTUP_FAILURE to skipped; guard null pageInfo
- STARTUP_FAILURE conclusion now falls through to the "skipped" branch
(matching mapRawCheckStateToStatus() REST default) instead of the
explicit failure enumeration catch-all
- Null pageInfo guard prevents TypeError from typeof null === "object"
JavaScript quirk when accessing hasNextPage on a null pageInfo field
- Tests added for both cases
* fix(scm-github): map COMPLETED+null conclusion to skipped not passed
When a CheckRun has status COMPLETED and conclusion null, the REST path's
mapRawCheckStateToStatus() converts it to "" which maps to "skipped".
The GraphQL path was incorrectly mapping it to "passed" via !rawConclusion.
Fix: only map rawConclusion === "SUCCESS" to "passed"; null falls through
to the else branch → "skipped", matching the REST path exactly.
|
||
|---|---|---|
| .changeset | ||
| .cursor | ||
| .github/workflows | ||
| .husky | ||
| artifacts | ||
| changelog | ||
| docs | ||
| examples | ||
| openclaw-plugin | ||
| packages | ||
| scripts | ||
| skills/agent-orchestrator | ||
| tests/integration | ||
| .eslintignore | ||
| .gitignore | ||
| .gitignore-template | ||
| .gitleaks.toml | ||
| .npmrc | ||
| .prettierignore | ||
| .prettierrc | ||
| AGENTS.md | ||
| ARCHITECTURE.md | ||
| CLAUDE.md | ||
| CONTRIBUTING.md | ||
| DASHBOARD_FIXES_SUMMARY.md | ||
| DESIGN-OPENCLAW-PLUGIN.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 | ||
| test-ao-config.yaml | ||
| test-ao-config2.yaml | ||
| tsconfig.base.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, Docker) · Tracker-agnostic (GitHub, Linear)
Quick Start
Prerequisites: Node.js 20+, Git 2.25+, tmux,
ghCLI. Install tmux viabrew install tmux(macOS) orsudo apt install tmux(Linux).
Install
npm install -g @composio/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
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
# Runtime data is auto-derived under ~/.agent-orchestrator/{hash}-{projectId}/
port: 3000
defaults:
runtime: tmux
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.
See agent-orchestrator.yaml.example for the full reference, or run ao config-help for the complete schema.
Plugin Architecture
Seven plugin slots. Lifecycle stays in core.
| Slot | Default | Alternatives |
|---|---|---|
| Runtime | tmux | process |
| Agent | claude-code | codex, aider, opencode |
| 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