agent-orchestrator/docs/cli/README.md

396 lines
22 KiB
Markdown

# AO CLI Foundation
This page is the running decision log for the Agent Orchestrator CLI. Keep new
CLI decisions here as the command surface grows.
## Current State
This branch implements the daemon-control foundation. AO now has a Go/Cobra
`ao` binary that can start, inspect, diagnose, and stop the local backend daemon
end to end.
What works now:
- `ao start` starts the daemon in the background and waits for `/readyz`.
- `ao status` and `ao status --json` report stopped, stale, unhealthy,
not-ready, or ready daemon state.
- `ao stop` gracefully stops the daemon via the loopback `POST /shutdown`
endpoint, only after verifying the daemon's identity from `running.json`.
- `ao daemon` is the hidden internal daemon entrypoint used by `ao start`.
- `ao doctor` (and `ao doctor --json`) checks config, data dir, the database
file's presence, daemon state, and local tool availability for `git`, `tmux`,
and `zellij`. It never opens or migrates the store — the daemon is the sole
writer/migrator, so doctor only reports whether the database exists yet.
- `ao completion` generates shell completions for `bash`, `zsh`, `fish`, and
`powershell`.
- `ao version` and `ao --version` print build metadata.
- `go run .` still works as a compatibility wrapper around `internal/daemon.Run`.
Manual smoke test:
```bash
cd backend
go build -o /tmp/ao ./cmd/ao
tmp=$(mktemp -d)
export AO_RUN_FILE="$tmp/running.json"
export AO_DATA_DIR="$tmp/data"
export AO_PORT=3037
/tmp/ao status --json
/tmp/ao doctor
/tmp/ao start
/tmp/ao status --json
/tmp/ao stop
/tmp/ao status --json
```
What is intentionally not implemented yet:
- `ao project ...`
- `ao spawn`
- `ao session ...`
- `ao send`
- `ao events ...`
Next steps:
1. Wire the existing project manager/controller shell into the daemon with a
durable SQLite-backed project store.
2. Implement `ao project list/add/show/remove` against `/api/v1/projects`.
3. Wire production Session Manager dependencies: project-backed repo resolver,
tmux/zellij runtime registry, first agent adapter, and AgentMessenger.
4. Add `/api/v1/sessions`, then implement `ao spawn`, `ao session ...`, and
`ao send`.
5. Add `/events` SSE and durable event-list reads, then implement
`ao events tail/list`.
## Decision
AO will use a single Go CLI binary built with
[Cobra](https://github.com/spf13/cobra).
The CLI is a thin client for the Go daemon. It should not call SQLite, runtime
adapters, agent adapters, workspace adapters, or SCM integrations directly. It
should start, discover, inspect, and command the daemon through the loopback API
and the existing `running.json` handshake.
Initial rules:
- The binary name is `ao`.
- `ao daemon` is the hidden/internal entrypoint for the long-running daemon.
- User-facing commands call the daemon over loopback after reading
`running.json`.
- Commands that mutate core AO state go through HTTP API routes, not direct
stores.
- Commands support predictable text output first and `--json` where automation
is likely.
- Do not introduce Viper in the foundation. Start with explicit flags and a
small config/client layer, then add config loading once the shape is real.
## References
These projects inform the direction, but AO should keep its own command surface
smaller at first.
| Project | CLI stack | What to take |
|---|---|---|
| [Gastown](https://github.com/gastownhall/gastown) | Go + Cobra, with Charmbracelet packages for richer terminal UI | Simple `cmd/<binary>/main.go` delegating to internal command construction. Useful confirmation that Cobra is the right default for this size of Go CLI. |
| [GitHub CLI](https://github.com/cli/cli) | Go + Cobra | Command factories, explicit IO streams, JSON output, and testable command construction. |
| [Docker CLI](https://github.com/docker/cli) | Go + Cobra | Daemon/client split, command groups, signal handling, and plugin-aware CLI layout. |
| [kubectl](https://github.com/kubernetes/kubectl) | Go + Cobra | Large command tree patterns and IO abstractions. It is a useful ceiling, not a shape to copy now. |
| [Tailscale CLI](https://github.com/tailscale/tailscale) | Go + ffcli | Useful daemon-backed product model: a CLI talks to a local daemon. Do not copy the framework choice. |
The old AO TypeScript CLI is a product/workflow reference only. We should not
port its implementation because it mixes CLI, storage, runtime, and project
logic in-process. The rewrite needs the CLI to sit outside the core daemon.
## Current Legacy CLI Inventory
Inventory source: installed `ao` binary at version `0.9.2`, plus the old
`packages/cli/src/program.ts` and `packages/cli/src/commands/*.ts` files.
Count:
- 25 public top-level commands, excluding Commander-generated `help`.
- 26 visible top-level commands if generated `help` is counted.
- 64 explicit public command nodes when nested subcommands are counted.
- 1 hidden internal command: `completion __complete`.
- No aliases are registered in the old Commander source.
Top-level commands:
| Command | Legacy purpose | Foundation decision |
|---|---|---|
| `start` | Start orchestrator agent and dashboard | Keep, but redefine as daemon start. |
| `stop` | Stop orchestrator agent and dashboard | Keep, daemon stop. |
| `status` | Show all sessions and project/session health | Keep, daemon and session status. |
| `spawn` | Spawn a single agent session | Keep after session API exists. |
| `batch-spawn` | Spawn many sessions | Defer. |
| `session` | Manage sessions | Keep a smaller subset after session API exists. |
| `send` | Send a message to a session | Keep after messaging API exists. |
| `acknowledge` | Agent self-reporting hook | Defer or replace with internal API. |
| `report` | Agent workflow transition hook | Defer or replace with internal API. |
| `review-check` | Trigger agents from review comments | Defer. |
| `review` | Manage AO-local reviewer runs | Defer. |
| `dashboard` | Start web dashboard | Defer to Electron/frontend lane. |
| `open` | Open terminal/dashboard | Defer. |
| `verify` | Verify issue after staging check | Defer. |
| `doctor` | Run install/env/runtime checks | Keep. |
| `update` | Upgrade AO | Defer to packaging/release lane. |
| `setup` | Configure integrations | Defer. |
| `plugin` | Plugin marketplace/install flow | Defer. |
| `notify` | Notification test commands | Defer. |
| `project` | Manage registered projects | Keep after project API exists. |
| `migrate-storage` | Legacy storage migration | Drop for rewrite unless a real migration appears. |
| `completion` | Generate shell completions | Keep. |
| `events` | Query activity event log | Keep a small `tail`/`list` surface after event API exists. |
| `config` | Read/write old global config | Defer. Avoid until config shape is stable. |
| `config-help` | Print old config schema | Drop. |
Nested legacy commands:
| Parent | Subcommands |
|---|---|
| `session` | `ls`, `attach`, `kill`, `cleanup`, `claim-pr`, `restore`, `remap` |
| `review` | `run`, `execute`, `send`, `list` |
| `setup` | `dashboard`, `desktop`, `webhook`, `slack`, `discord`, `composio`, `composio-slack`, `composio-discord`, `composio-discord-bot`, `composio-mail`, `openclaw` |
| `plugin` | `list`, `search`, `create`, `install`, `update`, `uninstall` |
| `project` | `ls`, `add`, `rm`, `set-default` |
| `events` | `list`, `search`, `stats` |
| `config` | `set`, `get` |
| `notify` | `test` |
| `completion` | `zsh`, hidden `__complete` |
## Initial Command Surface
The first CLI should make AO installable, startable, inspectable, and stoppable
before trying to recreate the old product surface.
### Foundation Commands
These are the first commands to implement.
| Command | Purpose | Notes |
|---|---|---|
| `ao start` | Start the daemon, wait for `/readyz`, and print PID/port. | Reads the same config env as the daemon. Should be idempotent when an existing healthy daemon is already running. |
| `ao stop` | Stop the running daemon. | Reads `running.json`, sends graceful termination, waits for run-file removal, and reports stale/dead daemon state clearly. |
| `ao status` | Show daemon status and, once APIs exist, project/session summary. | First version can show run-file, process liveness, `/healthz`, `/readyz`, uptime, and port. Add `--json`; add `--watch` once useful. |
| `ao daemon` | Hidden internal daemon entrypoint. | This replaces the current direct `go run .` daemon entrypoint once `main.go` is extracted into `internal/daemon`. |
| `ao doctor` | Diagnose the local environment. | Start with daemon/run-file/port checks, required binaries, config dir/data dir permissions, and runtime availability. |
| `ao completion` | Generate shell completions. | Cobra can support `bash`, `zsh`, `fish`, and `powershell`. |
| `ao version` | Print CLI and build metadata. | Implement as both `ao version` and Cobra's `--version` flag. |
This gives a useful first release even before project/session mutation routes are
complete.
### First Core Application Commands
These are the next commands once daemon HTTP routes expose the needed managers.
| Command | Purpose | Depends on |
|---|---|---|
| `ao project list` | List registered projects. | Project API. Alias `ls` is acceptable for old muscle memory. |
| `ao project add <path-or-url>` | Register a project. | Project API and project identity rules. |
| `ao project show <id>` | Inspect project config and health. | Project API. |
| `ao project remove <id>` | Archive/remove a project. | Project API. Alias `rm` is acceptable. |
| `ao spawn [issue]` | Spawn one coding-agent session. | Session Manager HTTP route, tracker lookup, workspace/runtime/agent adapters. |
| `ao session list` | List sessions across projects or one project. | Session API. Alias `ls` is acceptable. |
| `ao session show <session>` | Show one session with lifecycle, PR, CI, runtime, and paths. | Session API. |
| `ao session attach <session>` | Attach to the runtime terminal. | Runtime API or direct terminal attach contract exposed by daemon. |
| `ao session kill <session>` | Kill a session and clean up safely. | Session Manager `Kill`. |
| `ao session restore <session>` | Restore a terminated/crashed session. | Session Manager `Restore`. |
| `ao send <session> [message...]` | Send instructions to a running session. | AgentMessenger route. |
| `ao events tail` | Follow daemon activity events. | SSE/CDC API. |
| `ao events list` | List recent activity events. | Event read API. |
This is the smallest surface that covers the core product loop:
1. Register a repo.
2. Start AO.
3. Spawn work.
4. Inspect work.
5. Intervene in work.
6. Stop AO.
## Explicit Deferrals
Do not include these in the CLI foundation:
- `batch-spawn`: valuable, but it multiplies error handling before single-spawn
semantics are stable.
- `dashboard` and `open`: frontend/Electron should own the primary dashboard
launch path first.
- `review`, `review-check`, and `verify`: useful workflow automation, but not
required to run core AO.
- `setup`, `plugin`, and `notify`: integration/plugin surface should come after
the daemon API and config model settle.
- `update`: belongs with distribution and release packaging.
- `config` and `config-help`: wait for a stable Go config model. Avoid copying
the old TypeScript global config behavior.
- `migrate-storage`: old storage migration is not part of the rewrite unless a
concrete migration requirement appears.
- `acknowledge` and `report`: these are agent self-reporting hooks. Prefer a
daemon/internal protocol before exposing them as durable user CLI commands.
## Implementation Plan
1. Add Cobra to `backend/go.mod`.
2. Move current daemon startup from `backend/main.go` into
`backend/internal/daemon.Run(ctx, opts)`.
3. Add `backend/cmd/ao/main.go` as the only user binary entrypoint.
4. Add `backend/internal/cli` for command construction, IO streams, process
launching, run-file discovery, loopback HTTP client, and output formatting.
5. Implement `ao daemon` first so the current daemon behavior is preserved.
6. Implement `ao start`, `ao stop`, and `ao status` around `running.json` and
`/healthz`/`/readyz`.
7. Add `ao doctor`, `ao completion`, and `ao version`.
8. Add command tests using Cobra command construction with fake IO, fake process
runner, and fake daemon client. Keep daemon integration tests in the daemon
packages.
Suggested package layout:
```text
backend/
cmd/
ao/
main.go
internal/
cli/
root.go
start.go
stop.go
status.go
doctor.go
completion.go
version.go
client.go
output.go
process.go
daemon/
daemon.go
```
Acceptance criteria for the foundation:
- `go run ./cmd/ao daemon` behaves like today's `go run .`.
- `go run ./cmd/ao start` starts the daemon and waits until `/readyz` returns
ready.
- `go run ./cmd/ao status --json` works when the daemon is running, stopped, and
stale.
- `go run ./cmd/ao stop` gracefully stops the daemon and removes `running.json`.
- `go test ./...`, `go vet ./...`, and `go test -race ./...` pass.
## Implementation Readiness
This section records what the CLI can connect to in the current codebase and
what still needs to be built. Inventory date: 2026-05-31 after merging
`origin/main` at `438b830`.
### Implemented Foundation
The daemon-control foundation now exists in `backend/cmd/ao` and
`backend/internal/cli`.
Implemented commands:
- `ao daemon` hidden/internal daemon entrypoint.
- `ao start` starts the daemon, waits for `/readyz`, and supports `--json`,
`--timeout`, and `--log-file`.
- `ao stop` stops the daemon from `running.json`, removes stale run-files, and
supports `--json` and `--timeout`.
- `ao status` reports stopped/stale/unhealthy/not-ready/ready states and
supports `--json`.
- `ao doctor` checks config, data dir, database-file presence, daemon state, and
local tool availability for `git`, `tmux`, and `zellij`; supports `--json`. It
does not open or migrate the store (the daemon owns that).
- `ao completion` generates `bash`, `zsh`, `fish`, and `powershell`
completions.
- `ao version` prints build metadata.
The old `backend/main.go` remains as a compatibility wrapper around
`internal/daemon.Run`, so `go run .` still starts the daemon while scripts move
to `go run ./cmd/ao ...`.
### Already Implemented and Directly Usable by the CLI
These pieces are available now and are enough to build the daemon-management
part of the CLI.
| Area | Existing code | CLI use |
|---|---|---|
| Daemon config | `backend/internal/config` loads `AO_PORT`, `AO_REQUEST_TIMEOUT`, `AO_SHUTDOWN_TIMEOUT`, `AO_RUN_FILE`, and `AO_DATA_DIR`. Host is fixed to `127.0.0.1`. | `ao start`, `ao daemon`, `ao status`, and `ao doctor` can share the same config resolution. |
| HTTP server lifecycle | `backend/internal/httpd.Server` binds loopback, writes `running.json`, serves until context cancellation, then removes `running.json`. | `ao daemon` can preserve today's daemon behavior after extraction into `internal/daemon`. |
| Health probes | `GET /healthz` and `GET /readyz`. | `ao start` can wait for readiness; `ao status` and `ao doctor` can check daemon health. |
| Run-file handshake | `backend/internal/runfile` reads, writes, removes, and stale-checks `running.json`. | `ao status` can discover PID/port; `ao stop` can find the process; `ao start` can detect an already-running daemon. |
| Durable store | `backend/internal/storage/sqlite` opens SQLite, runs goose migrations, uses WAL, stores projects/sessions/PR/check/comment rows, and reads `change_log`. | Not directly called by user CLI commands, but confirms the daemon has a durable backend once APIs expose it. |
| CDC substrate | `backend/internal/cdc` poller and broadcaster exist; daemon starts the poller with `startCDC`. | Future `ao events tail` can build on this once an SSE/API transport exists. |
| Lifecycle manager | `backend/internal/lifecycle` is implemented and currently wired in daemon startup. | Session/status APIs can use it; CLI must wait for HTTP routes rather than calling it directly. |
| Reaper timer | `backend/internal/observe/reaper` exists and is wired. | Runtime liveness will be available once runtime registry wiring exists. |
### Implemented Internally but Not Reachable by CLI Yet
These are real backend components, but the CLI cannot responsibly use them until
they are wired into the daemon and exposed through HTTP.
| Area | Existing code | Missing before CLI can use it |
|---|---|---|
| Project API pieces | `internal/project` has manager/controller DTOs, `/api/v1/projects` routes exist, and `sqlite.Store` has project CRUD. | Durable project-store adapter/wiring in the daemon and CLI commands. The daemon currently constructs the router with nil API deps, so project routes are not product-usable from `ao` yet. |
| Session Manager | `backend/internal/session.Manager` implements `Spawn`, `Kill`, `Restore`, `List`, `Get`, `Send`, and `Cleanup`. | Production daemon wiring with real runtime, agent, workspace, messenger, and HTTP routes. |
| Runtime adapters | tmux and zellij adapters implement `ports.Runtime` and also have attach/send/output helpers. | Runtime registry wiring in daemon, attach/send abstractions in ports/API, and selection config. |
| Workspace adapter | git worktree adapter implements create/destroy/restore/list with safety checks. | Repo resolver backed by registered projects and daemon wiring into Session Manager. |
| GitHub issue tracker | `backend/internal/adapters/tracker/github` implements read-only issue `Get`, `List`, and `Preflight`. | Tracker registry/config, spawn prompt hydration, and project tracker metadata. |
| PR facts storage | SQLite PR/check/comment writes and CDC triggers exist. | SCM/PR observer that fetches GitHub PR/CI/review facts and calls `LCM.ApplyPRObservation`. |
| Session read model | `SessionManager.List/Get` derive display status from canonical lifecycle + PR facts. | HTTP response DTOs and API routes for CLI/frontend reads. |
### Still Missing
These are the main gaps before the full initial command set is real.
| Gap | Blocks |
|---|---|
| Product API client package with run-file discovery. | `project`, `spawn`, `session`, `send`, `events list`, richer `status`. |
| Shutdown mechanism choice: PID signal now, optional `POST /api/v1/daemon/shutdown` later. | `ao stop` polish and cross-platform behavior. |
| Session/send API route surface under `/api/v1`. | `spawn`, `session`, `send`, richer `status`. |
| Project API daemon wiring. | `ao project list/add/show/remove`. |
| SSE route for live CDC events plus durable catch-up reads. | `ao events tail`, frontend live updates. |
| Agent adapters for supported harnesses (`codex`, `claude-code`, etc.). | `ao spawn`, `ao session restore`. |
| AgentMessenger implementation over tmux/zellij. | `ao send`, LCM auto-nudge reactions. |
| Runtime registry wired with tmux/zellij. | Reaper liveness, `session attach`, spawn/kill/restore runtime work. |
| Notifier implementation/multiplexer. | Human notifications and LCM escalation side effects. |
| Activity hooks or agent self-report protocol. | Accurate working/idle/needs-input status beyond runtime/PR facts. |
| Project/tracker config model. | `project add/show`, tracker-backed `spawn`, `doctor` config checks. |
| OpenAPI/DTO/error contract. | Stable CLI/frontend API clients and tests. |
### Command Readiness Matrix
| Command | Can implement now? | Existing support | Remaining work |
|---|---:|---|---|
| `ao daemon` | Implemented | Current daemon startup is extracted to `internal/daemon.Run`. | None for foundation. |
| `ao start` | Implemented | Config, run-file stale check, HTTP readiness probes. | Later: package-manager/service integration if needed. |
| `ao stop` | Implemented | Run-file discovery gives PID/port; server exits cleanly on SIGINT/SIGTERM. | Optional later shutdown HTTP route. |
| `ao status` | Partially implemented | Run-file, process liveness via PID, `/healthz`, `/readyz`. | Rich project/session summary waits for `/api/v1/projects` and `/api/v1/sessions`. |
| `ao doctor` | Partially implemented | Config resolution, run-file, database-file presence (no open/migrate), runtime binary checks. | Deeper adapter preflights need daemon wiring/config and should be queried from the daemon, not run in-process. |
| `ao completion` | Implemented | Cobra generators. | None for foundation. |
| `ao version` | Implemented | Build metadata can be injected with `-ldflags`. | Release tooling needs to set metadata. |
| `ao project list/add/show/remove` | Not yet | Project manager/controller route shell and SQLite project CRUD exist. | Durable project-store adapter, daemon API wiring, and CLI HTTP client. CLI must not write SQLite directly. |
| `ao spawn` | Not yet | Session Manager exists; runtime/workspace/tracker pieces partly exist. | Agent adapters, registry/config wiring, project lookup, tracker hydration, HTTP route. |
| `ao session list/show` | Not yet | Store and Session Manager read model exist. | HTTP routes and response DTOs. |
| `ao session attach` | Not yet | tmux/zellij have attach command helpers. | Runtime attach port/API and terminal-launch policy. |
| `ao session kill/restore` | Not yet | Session Manager implements both. | Production wiring and HTTP routes. |
| `ao send` | Not yet | Session Manager has `Send`; tmux/zellij have send helpers. | AgentMessenger implementation, port/API wiring, busy/idle delivery policy. |
| `ao events tail/list` | Not yet | Durable `change_log`, CDC poller, in-process broadcaster. | SSE route and durable event-list route. |
### Recommended Build Order
1. Build CLI foundation around the daemon only: `daemon`, `start`, `stop`,
`status`, `doctor`, `completion`, `version`.
2. Wire the existing project manager/controller shell into the daemon with a
durable SQLite-backed store, then implement `project list/add/show/remove`.
3. Wire production Session Manager dependencies: project-backed repo resolver,
tmux/zellij runtime registry, first agent adapter, and AgentMessenger.
4. Add `/api/v1/sessions` and implement `spawn`, `session list/show/kill/restore`,
and `send`.
5. Add `/events` SSE plus event-list reads, then implement `events tail/list`.