* fix(daemon): self-heal a stale run-file instead of refusing to start On Windows the desktop supervisor can only TerminateProcess the daemon (no POSIX signal reaches a detached child), so the daemon's graceful shutdown never runs and ~/.ao/running.json is never removed. The leaked file survives into the next launch, and because Windows reuses PIDs aggressively the recorded PID usually belongs to an unrelated process. The startup pre-flight trusted PID liveness alone (runfile.CheckStale -> processalive.Alive), so it concluded a daemon was "already running" and exited with "refusing to start" on every restart. A dead daemon then makes the renderer's loopback REST calls (e.g. Spawn Orchestrator) fail silently. Verify the recorded port is actually served by an AO daemon with the recorded PID (a /healthz probe matching service + pid, the same ground truth inspectDaemon already uses) before refusing. A run-file left by a crashed, hard-killed, or reused-PID predecessor is treated as stale and overwritten, so startup is robust to a leaked run-file from any cause. Fixes #256 * fix(release): build the desktop daemon natively on each target OS build-daemon.mjs compiles the bundled `ao` daemon with the build host's GOOS and names it off the host platform (ao.exe only when the builder is Windows). The release workflow ran only on macos-latest, so a Windows package would ship a macOS binary named `ao` with no `ao.exe`, and the app could not launch a valid Windows daemon ("This program cannot be run in DOS mode" / binary not found). Run the release as a per-OS matrix (macOS + Windows) so host == target and each installer bundles a daemon compiled for its own platform, and pin the Go toolchain with setup-go since build-daemon needs it on every runner. Fixes #235 * feat(terminal): Windows ConPTY support for /mux attach Replaces the Windows stub in internal/terminal/pty_windows.go with a real ConPTY implementation backed by github.com/aymanbagabas/go-pty, so the daemon's /mux attach can stream a live terminal to the renderer on Windows. PTYSource.AttachCommand now returns (argv, env, err). On Windows the zellij attach is spawned directly (no powershell.exe wrapper) — wrapping ConPTY startup around a shell surfaces as modal application-error dialogs — and the per-session ZELLIJ_SOCKET_DIR is delivered via the spawn's CreateProcess env block instead of an 'env -u NO_COLOR' shim. Unix continues to use the env-shim wrapper and returns nil env. Adds go-pty v0.2.3 (+ bumps golang.org/x/sys to v0.44.0 transitively). Updates the in-process test fakes (terminal/fakes_test.go, httpd/terminal_mux_test.go) for the new signature. * feat(zellij): discover zellij binary on Windows and raise command timeout Defaults the zellij binary to whatever exec.LookPath finds first (preferring zellij.exe on Windows), falling back to LOCALAPPDATA\Programs\zellij\zellij.exe and ProgramFiles{,(x86)}\{zellij,Zellij}\zellij.exe so a fresh-installed Windows user gets a working runtime without setting Options.Binary. Raises the per-command timeout from 5s to 30s on Windows: the first zellij invocation after install routinely takes longer than 5s on Windows due to filesystem/AV warmup, which was causing benign DeadlineExceeded failures during session create. * feat(zellij,cli): Windows agent launcher trampoline for codex argv On Windows, zellij's KDL `args` quoting cannot round-trip codex's --config key=value flags (or any argv with embedded quotes), and shell-wrapping the agent in powershell/cmd quoting is equally unsound. This adds a small launch trampoline so zellij runs a known-fixed argv and the real argv is delivered out-of-band. How it works on Windows: 1. zellij.Runtime.writeLayout persists cfg.Argv to a temp JSON spec via the new agentlaunch package (AO_LAUNCH_SPEC env var points at the file). 2. The KDL layout runs the trampoline as `<ao.exe> launch` (windowsLaunchArgv); PATH is augmented so the trampoline resolves. 3. The new hidden `ao launch` subcommand reads the spec, deletes the temp file, and execs the real agent with cfg.Argv inside cfg.WorkspacePath. Also adds: - runner.Start fire-and-forget path (process_windows.go uses powershell.exe -EncodedCommand + Start-Process -WindowStyle Hidden with CREATE_NEW_CONSOLE so the daemon is not blocked on zellij's --create-background settling). - powerShellEncodedCommand helper and switch from -Command to -EncodedCommand for the existing powershell shellLaunchSpec (avoids brittle KDL→PowerShell quoting round-trips). Unix is unchanged: writeLayout passes cfg.Env straight through, createSession stays synchronous via runner.Run, and process_other.go is a stub that returns an error if anyone calls into the background path. * feat(codex): Windows binary resolution, terminal compat flags, TOML literal strings Three Windows-targeted refinements to the codex agent plugin so a default Windows install lands in a working state: 1. ResolveCodexBinary now follows .cmd/.ps1 shims to the underlying codex.exe (resolveNativeWindowsCodex + windowsNativeCodexCandidatesForShim). The npm-distributed codex shim cannot be exec'd directly under ConPTY without a shell wrapper; jumping straight to the .exe avoids that wrapper. 2. appendTerminalCompatibilityFlags adds Windows-specific args (e.g. --no-alt-screen) so codex's TUI renders correctly inside zellij's pane without the alternate-screen buffer churn that breaks ConPTY redraws. 3. hooks.go gains codexTOMLLiteralString / codexTOMLConfigString / containsTOMLControl so paths and other values with backslashes and quotes round-trip through codex's --config TOML parser using literal strings ('...') when basic strings would require unsafe escaping. * fix(lint): paramTypeCombine in pty_unix.go, revive doc comments in agentlaunch, codex test quotes * fix: stabilize windows zellij sessions --------- Co-authored-by: harshitsinghbhandari <24b4506@iitb.ac.in> Co-authored-by: Madhav <madhavkumar@microsoft.com> Co-authored-by: Vaibhaav <user@example.com> |
||
|---|---|---|
| .github/workflows | ||
| backend | ||
| docs | ||
| frontend | ||
| skills/bug-triage | ||
| test/cli | ||
| .envrc | ||
| .gitignore | ||
| .prettierignore | ||
| .prettierrc | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| DESIGN.md | ||
| README.md | ||
| flake.lock | ||
| flake.nix | ||
| package-lock.json | ||
| package.json | ||
README.md
ReverbCode
The orchestration layer for parallel AI coding agents. ReverbCode is a
Go-backed daemon that supervises many coding-agent sessions at once, each in
its own git worktree, and routes the feedback they need (CI failures, review
comments, merge conflicts) back to the right agent automatically. It ships with
an ao CLI and an Electron supervisor that both drive the same daemon over
loopback.
The Go module and packages remain agent-orchestrator; "ReverbCode" is the
public name.
See docs/architecture.md for the backend mental model
and AGENTS.md for the contributor / worker contract. For current
progress (what's shipped vs. in flight) see docs/STATUS.md.
What it does
- Agent-agnostic. A 23-adapter platform under
backend/internal/adapters/agent/(claude-code,codex,cursor,
opencode,aider,amp,goose,copilot,grok,qwen,kimi,
crush,cline,droid,devin,auggie,continue,kiro,kilocode,
and more), registered through a shared registry with common
activity-dispatch / hook utilities. The default is set byAO_AGENT. - Isolated workspaces. Worker and orchestrator sessions spawn into their own
git worktree(backend/internal/adapters/workspace/gitworktree/), launched
inside azellijruntime adapter (backend/internal/adapters/runtime/) so
every session has its own attachable terminal. - Live PR observation. The provider-neutral SCM observer
(backend/internal/observe/scm/) polls each session's PR with ETag guards and
semantic diffing, tracking CI/check runs and review threads, and feeds those
facts into the lifecycle manager, which sends the owning agent nudges for CI
failures, review feedback, and merge conflicts. GitHub is the implemented
provider today. - Durable facts, derived status. The SQLite store
(backend/internal/storage/sqlite/) persists a small set of session facts
plus PR/check/comment rows; display status is computed at read time, never
stored. DB triggers append every user-visible change tochange_log, and a
CDC poller/broadcaster (backend/internal/cdc/) feeds in-process subscribers
and an SSE replay endpoint. - Loopback-only daemon. The HTTP daemon (
backend/internal/httpd) controls
projects, sessions, orchestrators, and hook callbacks over127.0.0.1with no
auth, CORS, or TLS by design. - Lifecycle manager + reaper (
backend/internal/lifecycle/,
backend/internal/observe/reaper/) reduce runtime/activity/PR observations
into the durable session state and reclaim dead sessions.
How it works
- Register a local git repo as a project (
ao project add). - Spawn a worker session (
ao spawn), or an orchestrator that fans work out
across sessions. Each session gets its owngit worktreeand azellij
pane. - The agent develops, tests, and opens a PR from inside its worktree.
- The SCM observer watches that PR and routes feedback back to the agent: a CI
failure, a requested change, or a merge conflict becomes a nudge to the agent
that owns the PR. - You inspect, attach a terminal, and merge from the CLI or the Electron app;
human attention is needed only where the loop can't resolve on its own.
Extensibility
The backend is organized around inbound/outbound port contracts
(backend/internal/ports/) with swappable adapters under
backend/internal/adapters/:
| Port | Implemented adapters |
|---|---|
| Agent | 23 harnesses (see above) |
| Runtime | zellij |
| Workspace | git worktree |
| SCM | GitHub |
| Tracker | GitHub (adapter present; no runtime loop yet) |
| Reviewer | claude-code |
| Notifier | port defined; no shipped adapter yet |
See docs/STATUS.md for which lanes are live at runtime.
Quick start
Requirements: Go 1.25+, zellij on PATH for the
runtime adapter, and gh (or GITHUB_TOKEN) if you want the SCM observer to
authenticate against GitHub. The SQLite driver is the pure-Go
modernc.org/sqlite — no system SQLite library is required.
cd backend
go build -o /tmp/ao ./cmd/ao
# Start the daemon and wait for /readyz.
/tmp/ao start
# Register a local git repo as a project. The id defaults to the lowercased
# base of --path; pass --id explicitly when the directory name doesn't match.
/tmp/ao project add --path /path/to/your/repo --id your-repo --name your-repo
# Spawn a worker session running the default agent.
/tmp/ao spawn --project your-repo --prompt "Refactor the auth module"
# Inspect what's running.
/tmp/ao status
/tmp/ao session ls
Electron app (dev)
The desktop supervisor lives under frontend/ and is started separately:
cd frontend
npm install
npm run dev # electron-forge start
Heads-up: npm run dev does not start the daemon for you. Start it first
(ao start, see above) — the renderer attaches to the running daemon over
loopback (127.0.0.1:3001 by default, the AO_PORT from the table below).
Without a daemon the app opens but shows its daemon-not-ready state.
For renderer-only UI work without the Electron shell, use
npm run dev:web (Vite in a regular browser).
CLI surface
The CLI is intentionally thin: every product command resolves to a daemon HTTP
route. Run ao <command> --help for the authoritative flag shape; the table
below groups what's on main today.
| Lane | Command | Purpose |
|---|---|---|
| Daemon | ao start |
Start the daemon in the background and wait for /readyz. |
| Daemon | ao stop |
Graceful shutdown via loopback POST /shutdown. |
| Daemon | ao status |
Report PID/port/health/readiness from running.json. |
| Daemon | ao daemon |
Hidden internal entrypoint used by ao start. |
| Project | ao project add |
Register a local git repo as a project. |
| Project | ao project ls |
List registered projects. |
| Project | ao project get <id> |
Fetch one project. |
| Project | ao project set-config <id> |
Update per-project config. |
| Project | ao project rm <id> |
Remove a project. |
| Session | ao spawn |
Spawn a worker session in a registered project. |
| Session | ao session ls |
List sessions (filter by project, include terminated). |
| Session | ao session get <id> |
Fetch one session. |
| Session | ao session kill <id> |
Terminate a session. |
| Session | ao session rename <id> <name> |
Rename a session. |
| Session | ao session restore <id> |
Relaunch a terminated session. |
| Session | ao session cleanup |
Reclaim eligible workspaces for terminated sessions. |
| Session | ao session claim-pr <session> <pr> |
Attach an existing PR to a session. |
| Orchestrator | ao orchestrator ls |
List orchestrator sessions. |
| Messaging | ao send |
Send a message to a running agent session. |
| Utility | ao doctor |
Local health checks (config, data dir, DB, git, zellij). |
| Utility | ao completion <shell> |
Generate bash/zsh/fish/powershell completions. |
| Utility | ao version |
Print build metadata. |
| Internal | ao hooks <agent> <event> |
Hidden adapter hook callback. |
See docs/cli/ for the daemon-control intent and command shape.
Configuration
All configuration is env-driven; the daemon takes no config file. The bind
host is hard-coded to 127.0.0.1 — the daemon has no auth, CORS, or TLS, and
exposing it beyond loopback would be a security regression.
| Var | Default | Purpose |
|---|---|---|
AO_PORT |
3001 |
Bind port; daemon fails fast if taken. |
AO_REQUEST_TIMEOUT |
60s |
Per-request timeout (Go duration). |
AO_SHUTDOWN_TIMEOUT |
10s |
Graceful-shutdown hard cap. |
AO_RUN_FILE |
<UserConfigDir>/agent-orchestrator/running.json |
PID + port handshake path. |
AO_DATA_DIR |
<UserConfigDir>/agent-orchestrator/data |
SQLite DB, WAL files, managed state. |
AO_AGENT |
claude-code |
Default agent adapter id used by ao spawn. |
AO_SESSION_ID |
(unset) | Set inside spawned sessions; read by ao send and ao hooks. |
GITHUB_TOKEN |
(unset) | Used by the GitHub SCM and tracker adapters. Falls back to gh auth token. |
Health check:
curl localhost:3001/healthz
curl localhost:3001/readyz
Architecture
The daemon is a long-running supervisor. Adapters observe external facts (PR
state, agent activity, runtime liveness); the lifecycle manager reduces those
into a small set of durable session facts (activity_state, is_terminated,
PR rows). Display status is derived from those facts at read time — it is
never stored. SQLite triggers append every user-visible change to change_log,
and the CDC poller broadcasts those events to in-process subscribers and an
SSE stream.
Full mental model and load-bearing rules: docs/architecture.md.
Package-by-package ownership: docs/backend-code-structure.md.
Testing
The local gate is the backend Go build and race-enabled test suite:
cd backend && go build ./... && go test -race ./...
GitHub Actions is the authoritative pre-merge gate; mirror its commands here
when in doubt. See AGENTS.md for the regen workflow when
touching the daemon API surface (npm run sqlc, npm run api).
Status and roadmap
Progress tracking lives in docs/STATUS.md: what is shipped
on main today, what is still in flight, and the linked
rewrite
milestone on GitHub.
Contributing
Repo layout and the worker contract live in AGENTS.md. Keep
changes surgical, follow the package boundaries documented in
docs/backend-code-structure.md, and prefer
adding daemon HTTP routes over leaking storage / runtime into the CLI.