This document covers everything needed to reduce onboarding friction for Agent Orchestrator — from npm install to first agent spawn. It includes CLI consolidation, npm publishing, error handling, auto-detection, and UX design.
npm install -g @composio/aoao start <path-or-url>Every improvement in this doc serves one flow:
| Friction Point | Before | After | Which section fixes it |
|---|---|---|---|
| Install friction | npm install -g @composio/agent-orchestrator (40 chars) |
npm install -g @composio/ao (24 chars) + dashboard published, 1MB |
§15 npm Publishing |
Must run ao init before ao start |
13-prompt wizard, separate step | ao start auto-creates config |
§4 Commands Absorbed |
| Adding a second project requires manual YAML editing | Edit config by hand | ao start ~/other-repo |
§4 Commands Absorbed |
| Port conflicts | Fatal crash | Auto-find next free port | §15 npm Publishing |
| Permission errors (EACCES) | Fatal crash | Auto-retry with sudo | §15 npm Publishing |
| Too many CLI commands to learn | 16 commands, unclear which to use first | ao start + ao stop for 99% of cases |
§4, §14 Interactive UX |
Running ao start twice |
Starts duplicate dashboard | Interactive: "Already running — restart or open?" | §8 Already-Running |
| Agent doesn't know which runtime to use | Must manually configure plugin | Auto-detect installed runtimes | §12 Agent Runtime Detection |
| Agent can't figure out config | No documentation accessible at runtime | ao config-help + auto-injected system prompt |
§13 System Prompt Injection |
| Unclear error messages | Generic errors | Install-specific guidance (npm vs source) | §15 npm Publishing |
All commands are registered in packages/cli/src/index.ts via individual register*(program) calls using Commander.js.
| # | Command | File | Lines | Purpose |
|---|---|---|---|---|
| 1 | ao init |
commands/init.ts |
481 | Create agent-orchestrator.yaml from scratch (auto or interactive) |
| 2 | ao add-project |
commands/add-project.ts |
148 | Append a project to existing config |
| 3 | ao start [project] |
commands/start.ts |
557 | Start orchestrator + dashboard |
| 4 | ao stop |
commands/start.ts |
(shared) | Stop orchestrator + dashboard |
| 5 | ao status |
commands/status.ts |
— | Show session statuses with PR/CI info |
| 6 | ao spawn |
commands/spawn.ts |
— | Create agent session for an issue |
| 7 | ao batch-spawn |
commands/spawn.ts |
— | Spawn multiple agents at once |
| 8 | ao session |
commands/session.ts |
— | ls / kill / restore / claim-pr subcommands |
| 9 | ao send |
commands/send.ts |
— | Send message to agent session |
| 10 | ao review-check |
commands/review-check.ts |
— | Check PRs for review comments |
| 11 | ao dashboard |
commands/dashboard.ts |
— | Start dashboard standalone |
| 12 | ao open |
commands/open.ts |
— | Open session in terminal tab |
| 13 | ao lifecycle-worker |
commands/lifecycle-worker.ts |
— | Internal background polling worker |
| 14 | ao verify |
commands/verify.ts |
— | Mark issues as verified post-merge |
| 15 | ao doctor |
commands/doctor.ts |
— | Health checks + optional fixes |
| 16 | ao update |
commands/update.ts |
— | Update installation + rebuild + smoke test |
exec(), git(), gh(), tmux() wrappersfindWebDir(), port utils, dashboard envdetectDefaultBranch() — 3-method fallbackThese three commands share significant logic. The table below shows which capabilities exist in each:
| Capability | ao init |
ao add-project |
ao start (current) |
|---|---|---|---|
| Detect git repo & remote | ✓ | ✓ | ✓ (URL mode) |
| Detect default branch | ✓ | ✓ | ✓ (URL mode) |
| Detect project type (langs, frameworks) | ✓ | ✓ | — |
| Generate agent rules from templates | ✓ | ✓ | — |
| Generate session prefix | ✓ | ✓ | — |
| Create config YAML from scratch | ✓ | — | ✓ (URL mode) |
| Append project to existing config | — | ✓ | — |
| Load existing config | — | ✓ | ✓ |
| Resolve which project to use | — | — | ✓ |
| Start dashboard process | — | — | ✓ |
| Start orchestrator session | — | — | ✓ |
| Start lifecycle worker | — | — | ✓ |
| Clone repo from URL | — | — | ✓ |
| Interactive wizard prompts | ✓ | — | — |
ao start.
ao startao init → absorbed into ao startao start in a directory without a config should just create one and start. Init is only useful as a precondition to starting.
ao start| Logic Block | Source (init.ts) | Destination (start.ts) | How |
|---|---|---|---|
detectEnvironment() |
Lines 45–102 | Extracted to lib/detect-env.ts |
Shared helper — used by start's first-run path |
handleAutoMode() |
Lines 353–475 | Inlined into start's "no config found" branch | Becomes the default first-run behavior when no agent-orchestrator.yaml exists |
handleInteractiveMode() |
Lines 125–350 | Removed — auto-detection handles first run without wizard | Only on first run when user explicitly wants wizard. Optional — auto mode is default. |
detectProjectType() |
Import from project-detection.ts |
No change | Already a shared import — just add it to start.ts imports |
generateRulesFromTemplates() |
Import from project-detection.ts |
No change | Already a shared import |
--output and --interactive are unnecessary — ao start auto-creates config and auto-detects everything.
ao init command registration — becomes a deprecation wrapperao start --no-dashboard --no-orchestrator covers this if neededadd-project.ts does not currently exist on main branch. The logic described below may exist in a feature branch or PR. Verify which branch contains this code before implementation.
ao add-project <path> → absorbed into ao start <path>ao start ~/other-repo should detect that a config already exists, add the project if it's new, and start. There's no reason for a separate "add" step that doesn't start anything.
ao start| Logic Block | Source (add-project.ts) | Destination (start.ts) | How |
|---|---|---|---|
| Project-already-exists check | Line 48 | Start's path-resolution branch | If project already in config → just start it (don't error) |
| Session prefix uniqueness | Lines 74–87 | Shared with init logic in start | Reuse existing generateSessionPrefix() + collision check |
| Config append + YAML rewrite | Lines 128–130 | Start's "add new project" branch | Load config, add project entry, write back, then start |
| Git info detection | Lines 56–71 | Already exists in start (URL mode) | Reuse same detection — just apply it to path arg too |
| Project type detection | Lines 90–108 | Shared with init logic | Same detectProjectType() + generateRulesFromTemplates() |
--no-rules and --config are unnecessary — ao start auto-detects and auto-creates everything.
Currently ao add-project errors if the project already exists. In the new ao start <path>, if the project is already in the config, it simply starts it — no error. This is the correct UX: "start this project" should always work.
ao start <url> — already exists, no changehandleUrlStart(), resolveProjectByRepo()) stays exactly as-is. It already clones, auto-generates config if missing, and starts. The only addition: it will now also register in running.json.
running.json State FileNew file lib/running-state.ts — ~80 lines
ao stop uses lsof to find PIDs by port — fragile and OS-dependent. There's no way to detect "already running" or show other active orchestrators. A state file solves both.
// ~/.agent-orchestrator/running.json
[
{
"pid": 12345,
"projectId": "my-app",
"configPath": "/Users/me/my-app/agent-orchestrator.yaml",
"port": 3000,
"startedAt": "2026-03-15T10:30:00Z",
// No identifier field needed — one instance per machine
}
]
| Function | Purpose | Used by |
|---|---|---|
register(entry) |
Add entry on start, auto-prunes dead PIDs first | ao start |
unregister(pid) |
Remove entry on stop | ao stop |
getRunning() |
List all entries, auto-prune dead PIDs | ao stop, ao start, ao dashboard |
isAlreadyRunning(configPath, projectId) |
Check before starting — returns entry or null | ao start |
pruneStale() |
Remove entries whose PID no longer exists (kill(pid, 0)) |
Internal — called by register/getRunning |
ao startSee §8 Already-Running Behavior — Deep Dive for full details, interactive menus, running.json mechanics, and edge cases.
ao stop ImprovementsEdit commands/start.ts — ~30 changed lines
lsof for PID discovery. Users don't know what's still running after stopping one.
| Current | New |
|---|---|
Find PID via lsof -t -i :PORT |
Look up PID from running.json, fallback to lsof |
| No awareness of other orchestrators | After stopping: print list of other still-running orchestrators |
| No awareness of other projects | Print which projects were being managed before stopping |
| Silent after stop | Print confirmation: stopped project X on port Y (ran for Z) |
ao --get-config-generation-instructionNew file lib/config-instruction.ts — ~60 lines
ao --get-config-generation-instruction — direct CLI output (for piping into LLMs)generateOrchestratorPrompt() in ao-core — auto-injected into orchestrator system prompt so the orchestrator agent can help users configure AOSingle function getConfigInstruction(): string that returns a formatted text block. Registered as a root-level flag on the Commander program in index.ts (not a subcommand).
New file lib/caller-context.ts — ~40 lines
ao start should auto-open browser when called by a human, but NOT when called by the orchestrator agent. Currently there's no way to distinguish callers.
| Variable | Values | Set by |
|---|---|---|
AO_CALLER_TYPE |
human | orchestrator | agent |
Inferred at start, explicit at spawn |
AO_SESSION_ID |
e.g. myproj-42 |
Set when spawning agent sessions |
AO_PROJECT_ID |
e.g. my-app |
Set at start and spawn |
AO_CONFIG_PATH |
Absolute path to YAML | Set at start |
AO_PORT |
e.g. 3000 |
Set at start |
AO_DATA_DIR |
e.g. ~/.agent-orchestrator |
Set at start |
| Action | human | orchestrator | agent |
|---|---|---|---|
| Auto-open browser | Yes | No | No |
| Print banners & color | Yes | Minimal | No |
| Interactive prompts | Yes (if TTY) | Never | Never |
| JSON output preference | No | Yes | Yes |
export function getCallerType(): "human" | "orchestrator" | "agent"
export function isHumanCaller(): boolean
export function setCallerContext(env: Record<string, string>, opts: CallerContextOpts): void
| Command | Why It Stays | Any Minor Changes? |
|---|---|---|
ao status |
Unique purpose — session monitoring with PR/CI/review data. No overlap with start/stop. | None |
ao spawn |
Core workflow — creates agent sessions for specific issues. Distinct from starting the orchestrator. | Project arg becomes optional. Auto-resolves via AO_PROJECT_ID env var (set by ao start). Also sets AO_CALLER_TYPE on spawned sessions. |
ao batch-spawn |
Bulk variant of spawn for multiple issues at once. | Same env var injection as spawn |
ao session |
Session management subcommands (ls, kill, restore, claim-pr) — operational tooling. | None |
ao send |
Message delivery to agent sessions — no overlap with lifecycle commands. | None |
ao review-check |
PR review automation — specialized, no overlap. | None |
ao dashboard |
Standalone dashboard launch (useful when orchestrator runs separately). | Can read port from running.json if available |
ao open |
Terminal integration (iTerm tabs) — UI shortcut, no overlap. | None |
ao verify |
Post-merge issue verification — distinct workflow step. | None |
ao doctor |
Diagnostics — essential for troubleshooting, delegates to shell script. | None |
ao update |
Self-update mechanism — essential for source installs, delegates to shell script. | None |
ao lifecycle-worker |
Internal — spawned by start, not user-facing. | None |
Old commands continue to work but print a deprecation warning and delegate to the new code. This gives users and scripts time to migrate.
| Old Command | New Equivalent | Wrapper Behavior |
|---|---|---|
ao init |
ao start (first-run auto-creates config) |
Print: ⚠ 'ao init' is deprecated. Use 'ao start' instead — it auto-creates config on first run.Then call ao start --no-dashboard --no-orchestrator to create config without starting.
|
ao add-project <path> |
ao start <path> |
Print: ⚠ 'ao add-project' is deprecated. Use 'ao start <path>' instead.Then call ao start <path> --no-dashboard --no-orchestrator to add without starting.
|
// commands/init.ts (after rewrite)
export function registerInit(program: Command): void {
program
.command("init")
.description("[deprecated] Use 'ao start' instead")
.action(async (opts) => {
console.log(chalk.yellow("⚠ 'ao init' is deprecated. Use 'ao start' instead."));
// Delegate to start's config-creation logic
await createConfigOnly({ interactive: opts.interactive, output: opts.output });
});
}
When a user runs ao start for a project that is already running, the CLI must handle it gracefully instead of blindly starting a second instance.
Before starting anything, ao start reads ~/.agent-orchestrator/running.json and checks for a matching entry by configPath + projectId (and optionally identifier from --id).
// Pseudocode inside ao start, before any startup logic
const existing = findRunningEntry({
configPath: resolvedConfigPath,
projectId: resolvedProjectId,
});
if (existing) {
// Verify PID is actually alive (kill(pid, 0))
if (isProcessAlive(existing.pid)) {
printAlreadyRunning(existing);
process.exit(0);
} else {
// Stale entry — clean it up and proceed
unregister(existing.pid);
}
}
If AO is already running for the same project, behavior depends on who is calling:
Instead of just printing info and exiting, the CLI presents an interactive prompt:
$ ao start
ℹ AO is already running for this project.
Project: my-app
URL: http://localhost:3000
PID: 12345
Started: 2h 15m ago
? What would you like to do?
❯ Continue with the current one (open dashboard)
Start a new orchestrator on the same project
Override current config and restart
Quit
No flags needed. The human just picks an option. This is the primary UX.
Agents can't interact with prompts. They get a structured response and exit:
$ ao start
ℹ AO is already running for this project.
Project: my-app
URL: http://localhost:3000
PID: 12345
Started: 2h 15m ago
Config: /Users/me/my-app/agent-orchestrator.yaml
To restart: stop with 'ao stop', then 'ao start' again.
For agents, the structured output provides all the info needed to decide what to do next programmatically.
--override flag. When the user selects "Restart" from the interactive menu, the CLI handles it automatically:
running.jsonrunning.json$ ao start
⚠ Stopping existing instance (PID 12345, port 3000)...
✓ Stopped.
Starting fresh...
Dashboard: http://localhost:3000
Orchestrator: running
When user picks "Start a new orchestrator on the same project":
my-app-x7k2)my-app and my-app-x7k2$ ao start ~/my-app
ℹ An orchestrator is already running on this project.
Dashboard: http://localhost:3000
Sessions: 3 active | Up: 2h
? What would you like to do?
> Start a new orchestrator on the same project
✓ New orchestrator started on my-app
ID: my-app-x7k2
Dashboard: http://localhost:3000 (same dashboard)
2 orchestrators now running on my-app:
1. my-app — 3 sessions
2. my-app-x7k2 — 0 sessions (new)
add-project logic already does.
If a user runs ao start ~/other-repo (a different project) while AO is already running:
--config flag. The CLI auto-detects the config file. If it doesn't exist, it creates one. If it exists, it uses it.
| Priority | Source | Example |
|---|---|---|
| 1 (highest) | AO_CONFIG_PATH env var |
AO_CONFIG_PATH=~/prod.yaml ao start |
| 2 (default) | findConfigFile() auto-detection |
Searches cwd for agent-orchestrator.yaml or .yml |
The configPath stored in running.json is always the resolved absolute path. This means:
running.json state{
"pid": 12345,
"configPath": "/Users/me/my-app/agent-orchestrator.yaml",
"port": 3000,
"startedAt": "2026-03-15T10:30:00Z",
"projects": ["my-app", "my-app-x7k2", "backend"]
}
One AO instance. One port. Multiple projects managed via the config file and shown in the dashboard sidebar. No separate instances, no separate ports.
| User runs | State | Result |
|---|---|---|
ao start |
Nothing running, no config | Auto-create config, start dashboard + orchestrator |
ao start |
Nothing running, config exists | Start normally, register in running.json |
ao start |
Already running (human/TTY) | Interactive menu: Open dashboard / Restart / Quit |
ao start |
Already running (agent/non-TTY) | Print URL + info, exit 0 |
ao start |
PID in running.json but process is dead | Prune stale entry, start normally |
ao start |
Already running, user picks "New orchestrator" | Generate unique prefix, add as new config entry, spawn orchestrator session. Same port, same dashboard. |
ao start |
Already running, user picks "Override & restart" | Kill existing, remove config entry, start fresh |
ao start ~/other-repo |
AO running with different project | Add project to config, refresh dashboard sidebar |
ao start ~/other-repo |
Project already in config | Already managed — show dashboard URL |
ao start <github-url> |
Nothing running | Clone repo, auto-create config, start |
ao stop |
AO running | Stop everything — dashboard, orchestrator, lifecycle worker |
ao stop |
Nothing running | Print "nothing to stop" |
| Edge Case | Handling |
|---|---|
| running.json doesn't exist yet | Create it on first register() call. getRunning() returns []. |
| running.json is corrupted / invalid JSON | Log warning, treat as empty, overwrite on next register. |
| PID exists but is a different process (PID reuse) | Check process name/command if possible. Fallback: trust the entry if PID is alive. User can select "Restart" from the interactive menu to force a fresh start. |
| Machine rebooted (all PIDs stale) | pruneStale() runs on every getRunning() call — auto-cleans all dead entries. |
User kills process with kill -9 manually |
Entry stays in running.json but next ao start prunes it (PID dead) and starts normally. |
Two ao start commands run simultaneously (race) |
File-level write lock via lockfile or atomic write. Second caller retries once after 500ms. |
~/.agent-orchestrator/ directory doesn't exist |
Create it with mkdirSync(recursive: true) on first write. |
ao start Decision TreeAfter absorbing init and add-project, ao start handles every entry scenario:
| File | Action | Lines (est.) | Details |
|---|---|---|---|
lib/running-state.ts |
Create | ~80 | running.json state management — register, unregister, query, prune |
lib/caller-context.ts |
Create | ~40 | Caller type detection, env var setters/getters |
lib/config-instruction.ts |
Create | ~60 | Config schema documentation output for --get-config-generation-instruction |
commands/start.ts |
Heavy edit | 557 → ~700 |
Add: first-run config creation (from init), path-based project addition (from add-project),
already-running check with interactive menu, running.json registration, caller context env injection Refactor: Extract detectEnvironment() if inlining makes start.ts too large
|
commands/init.ts |
Rewrite → wrapper | 481 → ~15 | Deprecation warning + delegate to start's config-creation logic |
commands/add-project.ts |
Rewrite → wrapper | 148 → ~15 | Deprecation warning + delegate to start's add-project logic |
index.ts |
Minor edit | 43 → ~55 | Add --get-config-generation-instruction flag on root program. Registrations stay (wrappers still registered). |
stop (in start.ts) |
Edit | +30 | Use running.json for PID lookup, print confirmation with project details after stop |
init.ts — 481 linesadd-project.ts — 148 linesstart.ts — 557 linesstart.ts — ~700 lines (consolidated)init.ts — ~15 lines (wrapper)add-project.ts — ~15 lines (wrapper)running-state.ts — ~80 linescaller-context.ts — ~40 linesconfig-instruction.ts — ~60 linesao start$ cd ~/my-project
$ ao init # step 1: create config
$ ao start # step 2: start orchestrator
# Later, adding another project:
$ ao add-project ~/other-repo # step 3
$ ao start other-repo # step 4
User must know about init, start, AND add-project. Running ao start first gives a confusing "no config found" error.
$ cd ~/my-project
$ ao start # auto-creates config + starts
# Later, adding another project:
$ ao start ~/other-repo # auto-adds + starts
One command handles everything. Zero prerequisite knowledge needed.
$ ao start
# Starts a SECOND dashboard — two processes competing, confusion
$ ao start
# Already running on port 3000 (PID 12345)
# Started 2h ago. Dashboard: http://localhost:3000
# Select "Restart" from the menu to restart.
# Or: ao stop && ao start
$ ao stop
# Stopped. (silence — no idea if other
# orchestrators are still running)
$ ao stop
# ✓ Stopped AO on port 3000
# Projects managed: my-app, backend
# Uptime: 4h 12m
#
# Dashboard and all sessions terminated.
ao start flag inventory| Flag | Source | Purpose |
|---|---|---|
--no-dashboard |
Existing | Skip dashboard server |
--no-orchestrator |
Existing | Skip orchestrator agent |
--rebuild |
Existing | Clean and rebuild dashboard |
ao stop flag inventory| Flag | Source | Purpose |
|---|---|---|
--keep-session |
Existing | Keep session after stopping |
--purge-session |
Existing | Delete mapped session when stopping |
PR #474 introduces packages/core/src/plugin-registry.ts — a proper plugin registry that discovers plugins dynamically. We build on this, not duplicate it.
How plugins are discovered (3 sources):
packages/plugins/agent-* (shipped with AO)@composio/ao-plugin-agent-*Every agent plugin exports a detect() method. The CLI never needs to know how to detect each runtime — the plugin handles it.
// Plugin interface (in @composio/ao-core types)
interface AgentPluginManifest {
name: string; // e.g. "claude-code"
displayName: string; // e.g. "Claude Code"
priority: number; // Higher = preferred. e.g. 100 for claude, 90 for codex
detect(): boolean; // Can this runtime be found on the system?
}
// Example: claude-code plugin exports
export default {
name: "claude-code",
displayName: "Claude Code",
priority: 100,
detect() {
try { execSync("which claude", { stdio: "ignore" }); return true; }
catch { return false; }
},
create() { /* ... existing plugin factory ... */ }
};
ao start// In ao start, when auto-creating config:
async function detectAgentRuntime(registry: PluginRegistry): Promise<string> {
// 1. Get ALL registered agent plugins from the registry
const agentPlugins = registry.getAll("agent");
// 2. Filter to those that are detected on this system
const available = agentPlugins.filter(p => p.detect());
// 3. Selection logic
if (available.length === 0) {
console.warn("No agent runtimes detected. Using claude-code as default.");
return "claude-code";
}
if (available.length === 1) {
return available[0].name;
}
// Multiple available
if (isHumanCaller()) {
// Prompt: "Multiple runtimes detected. Which one?"
return await promptChoice(
available.sort((a, b) => b.priority - a.priority)
.map(p => ({ name: p.displayName, value: p.name }))
);
}
// Agent caller: pick highest priority
const selected = available.sort((a, b) => b.priority - a.priority)[0];
console.log(`Auto-selected: ${selected.displayName} (highest priority)`);
return selected.name;
}
| Scenario | What happens | CLI code change needed? |
|---|---|---|
New agent plugin added (e.g. agent-gemini) |
Plugin exports detect() + priority. Registry discovers it. ao start picks it up automatically. |
None |
| Third-party plugin via npm | User installs @composio/ao-plugin-agent-foo. Registry discovers it by naming convention. |
None |
| Custom local plugin | User specifies path in config. Registry loads it. | None |
| Plugin removed from system | detect() returns false. Skipped in auto-detection. |
None |
| Priority changes | Plugin updates its priority value. No CLI involvement. |
None |
ao --get-config-generation-instruction output must be the single source of truth, used both on-demand and auto-injected.
| Use Case | How It's Triggered | Who Uses It |
|---|---|---|
| On-demand | ao config-help or ao --get-config-generation-instruction |
Human or agent that wants to understand / modify the config |
| Auto-injected | generateOrchestratorPrompt() in ao-core |
Orchestrator agent session — knows how to create/modify configs for sub-agents |
lib/config-instruction.ts exports getConfigInstruction(): stringao config-help (subcommand) and ao --get-config-generation-instruction (flag) call the same functionao-core/src/orchestrator.ts → generateOrchestratorPrompt() imports and appends the instruction text to the system promptao config-help at runtime can self-correct bad configs.
For human callers (TTY detected), the CLI should always guide through interactive prompts rather than requiring flag knowledge. Flags exist as shortcuts for power users and as the API surface for agents.
| Scenario | Human (TTY) | Agent (non-TTY) |
|---|---|---|
| First run, no config | Auto-create config, show what was created, offer to customize | Auto-create config, print JSON summary, continue |
| Already running | Interactive menu: Continue / New orchestrator / Override & restart / Quit | Print structured info + flag hints, exit 0 |
| Multiple runtimes detected | Prompt: "Which agent runtime?" | Use heuristic, note choice in output |
| Stopping with others running | Print list, ask "Stop all?" | Stop default only, print remaining list |
| Config looks wrong | Suggest ao config-help for guidance |
Output config-help text inline |
Every CLI command should pass this test: Can an agent with zero prior knowledge of AO figure out what to do next from the CLI output alone?
PR #463 already fixes the major npm publishing blockers:
| Problem | Before | After (PR #463) |
|---|---|---|
@composio/ao-web dashboard |
private: true — not published, npm users get fatal crash |
Published on npm with production entry point (start-all.js) |
| Package size | 67 MB (includes .next/cache bloat) |
~1 MB (optimized files field) |
| Dashboard startup | npx next start (slow, downloads next) |
node_modules/.bin/next (instant, local binary) |
node-pty dependency |
Required — crashes on install failure | Optional — direct terminal gracefully degrades |
| Dev vs production detection | Hardcoded to dev mode | Auto-detects: source users get pnpm run dev, npm users get node dist-server/start-all.js |
findWebDir() errors |
Generic "Run: pnpm install" | Install-specific: npm users see npm install -g, source users see pnpm install && pnpm build |
| Permission errors (EACCES) | Fatal crash | Auto-retries with sudo when interactive |
git clone ...
cd agent-orchestrator
pnpm install
pnpm build
npm link
ao init # 13 prompts
ao start # port crash
# manually edit YAML...
npm install -g @composio/ao
ao start ~/my-project # or ao start https://github.com/org/repo
2 steps. That's it.
Config auto-created. Dashboard starts. Zero prompts.
The CLI consolidation (absorbing init and add-project into start) is what makes the 3-step npm experience possible:
ao init step — ao start auto-creates config on first runao add-project step — ao start ~/other-repo auto-adds to config| Package | npm Name | Purpose | Install |
|---|---|---|---|
| CLI | @composio/ao renamed |
Main entry point — ao binary. Short name for frictionless install. |
npm install -g @composio/ao |
| Core | @composio/ao-core |
Config loading, session management, lifecycle | Dependency of CLI |
| Web/Dashboard | @composio/ao-web |
Next.js dashboard + direct terminal | Dependency of CLI |
| Plugins | @composio/ao-plugin-* |
Agent runtime plugins (claude-code, codex, opencode, gemini) | Dependencies of CLI |
@composio/ao instead of @composio/agent-orchestrator. The unscoped ao is taken on npm, but @composio/ao is available. Keep @composio/agent-orchestrator as a deprecated redirect package.ao-web should ship pre-built (next build output) so npm users don't need to buildnpm install -gao start should not need to download anything elseao update should handle both npm (npm update -g) and source (git pull && pnpm build) installsao spawnao spawn <project> <issue> requires the project name as the first argument. But if you’re running this from an orchestrator terminal, the project context is already known. Requiring it is unnecessary friction.
ao spawn my-app #42
ao spawn my-app INT-1234
Project name always required. User must remember the exact config key.
ao spawn #42
ao spawn INT-1234
Project auto-detected from AO_PROJECT_ID. Just pass the issue.
Simple — no fancy heuristics needed:
AO_PROJECT_ID env var is set → use it. This covers 99% of cases because ao start sets this variable and every child process (orchestrator, agent sessions) inherits it.ao spawn <project> <issue>”// CURRENT in spawn.ts
.argument("<project>", "Project ID from config")
.argument("[issue]", "Issue identifier")
// NEW — project becomes optional
.argument("<issue>", "Issue identifier (e.g. #42, INT-1234)")
.argument("[project]", "Project ID — auto-detected if omitted")
// In registerSpawn action handler:
function resolveProjectForSpawn(
config: OrchestratorConfig,
arg1: string,
arg2?: string
): { projectId: string; issueId: string } {
// Two args: explicit project + issue (backward compat)
if (arg2) {
return { projectId: arg1, issueId: arg2 };
}
// One arg: auto-detect project, arg1 is the issue
const issueId = arg1;
// 1. Check AO_PROJECT_ID env var
if (process.env.AO_PROJECT_ID) {
return { projectId: process.env.AO_PROJECT_ID, issueId };
}
// 2. Single project in config
const projectIds = Object.keys(config.projects);
if (projectIds.length === 1) {
return { projectId: projectIds[0], issueId };
}
// 3. Ambiguous — error
throw new Error(
`Multiple projects in config: ${projectIds.join(", ")}
` +
`Run from an orchestrator session, or specify:
` +
` ao spawn <project> <issue>`
);
}
Items that need owner input before implementation:
| # | Question | Options | Recommendation |
|---|---|---|---|
| 0 | Rename npm package from @composio/agent-orchestrator to @composio/ao? |
Rename (shorter) vs keep (established) | Rename — @composio/ao is available. Old package becomes a deprecated redirect. Every character counts in install friction. |
| 1 | Should ao config-help be a subcommand or keep the long flag --get-config-generation-instruction? |
Subcommand (shorter) vs flag (as discussed) | Both — ao config-help as alias, long flag kept for backward compat |
| 2 | What default priority values should each agent plugin ship with? | Each plugin sets its own priority number. Higher = preferred when multiple are detected. |
claude-code: 100, codex: 90, aider: 80, opencode: 70, gemini: 60, cursor: 50 (can be overridden per-plugin) |
| 3 | Should the --smart flag be removed immediately or deprecated first? |
Hard remove vs deprecation wrapper | Hard remove — owner confirmed nobody uses it |
| 4 | Should running.json live in ~/.agent-orchestrator/ or alongside the project config? |
Global state dir vs project-local | Global (~/.agent-orchestrator/running.json) — enables cross-project awareness |
| 5 | For the interactive "already running" menu, should "Open dashboard" auto-open browser or just print the URL? | Auto-open vs print | Auto-open (human chose the action explicitly) |