Agentic orchestrator for parallel coding agents — plans tasks, spawns agents, and autonomously handles CI fixes, merge conflicts, and code reviews.
Go to file
Harshit Singh Bhandari 1983e97bfa
fix(daemon): stop orchestrator id-increment + OS-native daemon liveness link (#2185)
* fix(daemon): do not tear down live sessions on shutdown; adopt them on boot

Remove SaveAndTeardownAll from the graceful shutdown path. Live tmux/ConPTY
sessions survive daemon exit; Reconcile on the next boot adopts them via
reconcileLive, preserving session IDs and preventing the id-increment bug.
Add runShutdownSessionLifecycle as a testable seam and narrow the
sessionLifecycle interface to Reconcile+RestoreAll only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(daemon): make shutdown-teardown regression test falsifiable

Re-add SaveAndTeardownAll to the sessionLifecycle interface and to
fakeSessionLifecycle so TestShutdown_DoesNotCallSaveAndTeardownAll is
genuinely falsifiable: the flag flips if runShutdownSessionLifecycle
ever calls sl.SaveAndTeardownAll, making the assertion meaningful.
Name the sl parameter (was discarded with _) so the seam is visible.

RED: with a temporary sl.SaveAndTeardownAll call, test fails.
GREEN: without it, test passes. go test ./internal/daemon/... -race: 23/23.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(daemon): guard shutdown-teardown at compile time via narrowed interface

Remove SaveAndTeardownAll from sessionLifecycle so daemon.Run physically
cannot call teardown on shutdown. Delete the no-op runShutdownSessionLifecycle
seam and its test. The narrowed interface is the guard: re-introducing teardown
requires a visible, reviewable interface change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(session): make ensure-orchestrator idempotent so POST cannot mint a duplicate

When clean=false, SpawnOrchestrator now checks for an existing active orchestrator
and returns it directly instead of always calling Spawn. Adds RED/GREEN tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(session): drop redundant NoCleanSkipsKills, covered by SpawnsWhenNoneExists

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(daemon): supervisor watchdog

Adds backend/internal/daemon/supervisor: a transport-agnostic watchdog
that fires onLastClientGone() exactly once when the live connection count
drops to zero and stays zero for a configurable grace period. Arms only
after the first accepted connection (headless safety: CLI ao start never
self-stops). Reconnect before grace elapses cancels the pending timer.
Mutex guards liveCount/armed/pendingTimer; sync.Once guards the callback.
Tests use net.Pipe + a fake listener (no OS sockets); all three behavior
contracts verified with -race: never fires pre-connect, fires once on
last disconnect, reconnect within grace cancels and re-arms correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(daemon): supervisor watchdog review fixes (data race, ErrClosed, leak, cleanup)

- Fix data race: capture liveCount into local inside lock before logging
- Replace io.EOF with errors.Is(err, net.ErrClosed) for correct production behavior; update fakeListener to match
- Fix goroutine leak: derive cancellable child context in Serve so watcher always unblocks on return
- Simplify makePipe: drop dead error return, update 3 call sites
- Remove dead defensive pendingTimer nil-check in armGrace

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(daemon): OS-native supervisor listener triggers clean shutdown

Creates platform-split Listen() in supervisor package (Unix UDS sibling
of run-file; Windows named pipe via go-winio). Wires it into daemon.Run
before srv.Run: listener failure is non-fatal so headless ao-start works.
Adds RequestShutdown() on Server and three unit tests for listen_unix.go.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(daemon): log supervisor Serve error instead of discarding it

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(desktop): supervisor link; daemon self-stops (clean) on frontend exit

Add connectSupervisor() in frontend/src/main/supervisor-link.ts: holds one
client connection to the daemon's OS-native supervisor socket (Unix domain
socket on macOS/Linux, named pipe on Windows). Retries with bounded backoff
until the daemon is up; reconnects automatically on drop.

In main.ts, connect the link from reportBoundPort (once per daemon ready
transition). Remove the quit-time daemon teardown: the before-quit handler
now only disposes the browser view. The process.on("exit") killDaemon call
is also removed. When Electron exits for any reason the OS closes the fd,
the daemon detects EOF, and self-stops after its 5s grace period. tmux and
ConPTY sessions survive and are adopted on the next boot.

killDaemon and stopDaemon are kept for the explicit user-stop path
(ipcMain.handle("daemon:stop", ...)).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(desktop): guard against daemon orphan when supervisor link is down; tidy test setup

Expose a live `connected` getter on SupervisorLinkHandle and add a
last-resort process.on("exit") kill that fires only when the link is
not actually connected (UDS never bound or addr was null), preventing
orphan daemons while keeping the OS-fd teardown path for the normal
case. Log a warning when addr is null so the skip is diagnosable.
Invert the setup.ts guard to the natural form, dropping the empty
if/else skeleton.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(core): restore promptless sessions in place (reboot recovery, no increment)

Drop the empty-prompt early return in restoreArgv that returned ErrNotResumable
when the adapter could not resume and no prompt was saved. Now control falls
through to GetLaunchCommand unconditionally: a saved prompt is replayed, an
empty prompt (orchestrator) launches fresh with the system prompt only, same id,
same workspace. Removes the only producer of ErrNotResumable and deletes the
now-dead sentinel, its service mapping, and the corresponding tests.

Frontend reference to SESSION_NOT_RESUMABLE in TerminalPane.tsx is left intact
(the handler becomes harmlessly dead; the API simply stops returning that code).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(integration): dead-live session is restored, not abandoned, after reconcile

Task 5 made promptless sessions relaunch fresh instead of hitting ErrNotResumable.
The reconcile crash-recovery path (documented in reconcileLive) terminates a
dead-live session then RestoreAll relaunches it on the same boot. This test
asserted the old promptless-stays-terminated artifact; update it to the intended
restored end state (live again, same id, one fresh runtime Create).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* style(session): replace em dashes in service.go messages and comment

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(desktop): scope supervisor link to spawn path; dispose link on explicit stop

Document that the liveness link is established only when the app spawned the
daemon: the attach path intentionally does not link, to keep an `ao start`
daemon persistent (headless safety). Also dispose the link on an explicit
daemon:stop so its reconnect loop does not retry a deliberately-stopped daemon.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: format with prettier [skip ci]

* docs: add daemon-lifecycle adopt-on-shutdown implementation plan

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: format with prettier [skip ci]

* fix(daemon): keep supervisor watchdog alive across transient Accept errors

Review of #2185: Serve previously returned on the first unexpected Accept error,
silently disabling the watchdog (the 'restart is caller's job' comment described
a contract the caller never fulfilled). Back off and keep accepting instead, so
a transient error (e.g. EMFILE) cannot leave the daemon unable to self-stop on
frontend death. Also drop the stale Task 3/4 planning comment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(core): leave promptless workers terminated on restore (orchestrators still relaunch fresh)

A promptless, unresumable KindWorker session had no prompt to replay and
no native session id to resume from. Blank-relaunching it via GetLaunchCommand
would silently drop its task. restoreArgv now returns ErrNotResumable for this
case, gated on (meta.Prompt == "" && kind != domain.KindOrchestrator). Orchestrators
are promptless by design and continue to relaunch fresh with the system prompt only.
Re-introduces ErrNotResumable sentinel and its Conflict API mapping.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(desktop): re-link supervisor on attach for app-owned daemons (close lingering-daemon gap)

- runfile.Info gets Owner field (omitempty): "app" when Electron spawned,
  empty for headless `ao start`. server.go reads AO_OWNER env to set it.
- daemonEnv() injects AO_OWNER=app so spawned daemons self-identify.
- Extracted establishSupervisorLink() from inline reportBoundPort code.
  Spawn path calls it unconditionally; attach paths call it only when
  shouldLinkOnAttach(owner) is true (owner === "app").
- Both attach paths (inspectExistingDaemon, resolveDaemonFromPort) now
  read the owner from the run-file and re-link when app-owned, preventing
  a lingering app-spawned daemon from self-stopping mid-session.
- Headless ao start daemons stay unlinked: persistent across app quit.
- New daemon-owner.ts + daemon-owner.test.ts (4 vitest cases, all pass).
- Go: 9 tests pass with -race; vet clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: quiet expected ErrNotResumable log in RestoreAll; note attach TOCTOU

Review polish on #2185: a promptless worker left terminated on boot is expected,
not an operational error, so log it at Warn not Error. Document the narrow
run-file re-read TOCTOU on the port-attach re-link path (worst case is linking a
headless daemon; the dispose-idempotency guard prevents any leak).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* style: gofmt the review-fix files (CI format/lint check)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-26 18:28:32 +05:30
.github/workflows fix(desktop): package Windows via NSIS instead of Squirrel (#403) 2026-06-23 20:44:30 +05:30
backend fix(daemon): stop orchestrator id-increment + OS-native daemon liveness link (#2185) 2026-06-26 18:28:32 +05:30
docs fix(daemon): stop orchestrator id-increment + OS-native daemon liveness link (#2185) 2026-06-26 18:28:32 +05:30
frontend fix(daemon): stop orchestrator id-increment + OS-native daemon liveness link (#2185) 2026-06-26 18:28:32 +05:30
screenshots docs: refresh repository readme [skip ci] (#2190) 2026-06-26 17:28:07 +05:30
scripts chore: add daemon build script (#365) 2026-06-21 19:45:27 +05:30
skills/bug-triage fix(skills): adapt bug-triage skill to ReverbCode stack (#281) 2026-06-17 22:27:25 +05:30
test/cli chore: add prettier config and CI auto-formatter (#166) 2026-06-10 09:09:17 +05:30
.envrc Add agent adapters and wire per-session agents into the session manager (#65) 2026-06-02 16:51:32 +05:30
.gitignore Zellij to tmux + ConPTY runtime, session save/restore, crash-proof reconcile (port #404) (#2183) 2026-06-25 15:05:41 +05:30
.prettierignore chore: add prettier config and CI auto-formatter (#166) 2026-06-10 09:09:17 +05:30
.prettierrc chore: add prettier config and CI auto-formatter (#166) 2026-06-10 09:09:17 +05:30
AGENTS.md feat(frontend): add live browser panel (#375) 2026-06-22 04:14:18 +05:30
CLAUDE.md feat(frontend): add live browser panel (#375) 2026-06-22 04:14:18 +05:30
DESIGN.md feat(renderer): full-width shell topbar; retire per-view topbars and review dashboard (#195) 2026-06-12 15:20:04 +05:30
README.md docs: refresh repository readme [skip ci] (#2190) 2026-06-26 17:28:07 +05:30
ao-dashboard-preview.png docs: refresh repository readme [skip ci] (#2190) 2026-06-26 17:28:07 +05:30
ao-logo.svg docs: refresh repository readme [skip ci] (#2190) 2026-06-26 17:28:07 +05:30
flake.lock Add agent adapters and wire per-session agents into the session manager (#65) 2026-06-02 16:51:32 +05:30
flake.nix Add agent adapters and wire per-session agents into the session manager (#65) 2026-06-02 16:51:32 +05:30
package-lock.json feat: ao session claim-pr + spawn --claim-pr wiring (#101) 2026-06-06 00:01:03 +05:30
package.json chore: add prettier config and CI auto-formatter (#166) 2026-06-10 09:09:17 +05:30

README.md

Agent Orchestrator

Agent Orchestrator

The orchestration layer for parallel AI coding agents

Stars Contributors Twitter Discord License: Apache-2.0

An Agentic IDE that supervises parallel AI coding agents in isolated workspaces, with complete control and automatic feedback loops from CI failures, review comments, and merge conflicts.

Agent Orchestrator Dashboard

See AO in Action (before the re-write)

First

Visit
Second

Visit
Third

Visit
Fourth

Visit

FeaturesQuick StartArchitectureDocumentationContributing


Features

Feature Description
Agent-Agnostic Platform 23+ agent adapters including Claude Code, OpenAI Codex, Cursor, OpenCode, Aider, Amp, Goose, GitHub Copilot, Grok, Qwen Code, Kimi Code, Cline, Continue, Kiro, and more
Isolated Workspaces Each session spawns into its own git worktree with dedicated runtime
Platform-Native Runtimes tmux on Darwin/Linux, conpty on Windows for optimal performance
Live PR Observation Provider-neutral SCM observer with automatic feedback routing
Automatic Feedback Routing CI failures, review comments, and merge conflicts routed to the owning agent
Durable Facts Storage SQLite persists immutable facts with display status derived at read time
CDC Broadcasting DB triggers append changes to change_log, broadcasted via SSE
Desktop Experience Native Electron app with React UI and live terminal streaming
Loopback-Only Daemon HTTP control over 127.0.0.1 with no auth, CORS, or TLS by design

Supported Agents

Works with 23+ CLI-based coding agents including Claude Code, OpenAI Codex, Cursor, OpenCode, Aider, Amp, Goose, GitHub Copilot, Grok, Qwen Code, Kimi Code, Crush, Cline, Droid, Devin, Auggie, Continue, Kiro, and Kilo Code.

If it runs in a terminal, it runs on Agent Orchestrator.


Quick Start

Prerequisites

Requirement Minimum Recommended
Go 1.25+ Latest
Node.js 20+ Latest LTS
Git Any Latest
pnpm Any Latest

Optional:

  • tmux (Darwin/Linux) - For Unix runtime
  • gh (GitHub CLI) - For authenticated GitHub API calls

Installation

Download the latest release for your platform:

Platform Download
Windows Setup.exe
macOS Agent Orchestrator.dmg
Linux Agent Orchestrator.AppImage

Direct Download: Latest Release


Telemetry

Agent Orchestrator collects minimal telemetry for reliability and product understanding. Data is stored locally by default; remote transmission is opt-in via environment variables. Read the full telemetry policy.


Architecture

Agent Orchestrator is a long-running Go daemon built around inbound/outbound port contracts with swappable adapters.

Core mental model: OBSERVE external facts → UPDATE durable facts → DERIVE display status / ACT

Key components:

  • Frontend - Electron + React UI with TanStack Router/Query and shadcn/ui
  • Backend Daemon - Go-based HTTP server with controllers, services, and adapters
  • Runtime - Platform-specific: tmux on Darwin/Linux, conpty on Windows
  • Storage - SQLite with change-data-capture (CDC) for real-time updates
  • Adapters - 23+ agent adapters, git worktree workspace, GitHub SCM integration

For detailed architecture diagrams, data flows, and load-bearing rules, see architecture.md.


Documentation

Document Description
Architecture System architecture, data flows, and load-bearing rules
Backend Code Structure Package-by-package ownership and dependency rules
AGENTS.md Contributor and worker-agent contract
Agent Adapter Contract Agent adapter interface and hook behavior

Testing

# Backend tests
cd backend
go test -race ./...

# Frontend tests
cd frontend
pnpm test

# Full CI validation locally
npx @redwoodjs/agent-ci run --all

Configuration

All configuration is environment-driven. The daemon takes no config file.

Variable Default Purpose
AO_PORT 3001 HTTP bind port
AO_REQUEST_TIMEOUT 60s Per-request timeout
AO_SHUTDOWN_TIMEOUT 10s Graceful shutdown cap
AO_RUN_FILE ~/.ao/running.json PID/port handshake
AO_DATA_DIR ~/.ao/data SQLite data directory
AO_AGENT claude-code Compatibility agent adapter
GITHUB_TOKEN - GitHub auth token

Health Checks

curl localhost:3001/healthz   # Liveness probe
curl localhost:3001/readyz    # Readiness probe

Contributing

We love contributions! Join our community on Discord to get started.

Join us on Discord

Discord

Daily contributor sync: Every day at 10:00 PM IST

Get your issues verified by core contributors, ask questions, share progress, and learn from the community. New contributors are always welcome!

Why join Discord?

  • Get your issues and PRs verified by core contributors before investing time
  • Learn from experienced contributors in daily sync calls
  • Share your progress and get feedback
  • Get help troubleshooting in real-time
  • Stay updated on the latest developments and roadmap

Quick Start

  1. Join the Discord - Connect with the community and get guidance
  2. Read the contributor contract - See AGENTS.md for repo layout, daemon/API boundaries, and coding conventions
  3. Pick a focused problem - Browse open issues and choose one small enough for a focused PR
  4. Open a clear PR - Keep changes narrow, explain user-visible impact, link issues, include tests
  5. Iterate with contributors - Use review feedback to tighten the PR until verified

License

Apache License 2.0 - see LICENSE for details.


Star us on GitHubReport IssuesDiscussions

Made with love by the Agent Orchestrator community