From 5d0b624fbef5668d82672179c48309ea74403933 Mon Sep 17 00:00:00 2001 From: Harshit Singh Bhandari Date: Fri, 22 May 2026 16:36:31 +0530 Subject: [PATCH] docs: clarify one-orchestrator-per-project model + prune superseded docs (closes #2005) (#2012) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: clarify one-orchestrator-per-project model (closes #2005) The architecture design doc defined "Orchestrator" only as "the central server that manages everything," conflating the central daemon with the per-project orchestrator agent. This implied a single orchestrator agent spanning all projects. The actual (and intended) model: one central daemon (`ao start`) manages all registered projects, but each project gets its own orchestrator agent session — one per project, enforced by the canonical orchestrator in session-manager (ensureOrchestrator). Disambiguate the term in the nomenclature table and the server-centric design principle. Co-Authored-By: Claude Opus 4.7 (1M context) * docs: remove stale design/architecture artifacts (closes #2005) Delete historical design research, implementation specs, and PR-handoff notes that are superseded by the shipped code and the living docs (DESIGN.md, CLAUDE.md, docs/ARCHITECTURE.md, docs/DEVELOPMENT.md). None were linked from any current doc. Removed: - artifacts/ — original design research (incl. architecture-design.md, the doc #2005 flagged for the misleading "one central orchestrator" framing; removing it resolves the ambiguity at the source) - docs/design/ — Feb 2026 dashboard design-research artifacts - docs/specs/ + design-*.html — implemented feature specs/analyses - docs/state-machine-stage2-evidence-recovery-plan.md — shipped plan - docs/SECURITY-AUDIT-SUMMARY.md — point-in-time audit snapshot - handoff/pr-1466/ — handoff notes for a long-merged PR Drop the now-dangling artifacts/ entry from .cursor/BUGBOT.md ignore list. Confirms the one-orchestrator-per-project model: code enforces a single canonical orchestrator per project (ensureOrchestrator); no multiple orchestrators within one project. * docs: restore docs flagged as still-relevant by review (#2005) @yyovil flagged several docs as still needed. Restore the full artifacts/ and docs/design/ trees, docs/specs/project-based-dashboard- architecture.md, and handoff/pr-1466/ (kept whole for link cohesion). artifacts/architecture-design.md is kept WITH the one-orchestrator-per- project clarification (the #2005 fix) rather than deleted — resolving the ambiguity in place. Restores the artifacts/ entry in .cursor/BUGBOT.md. Remaining removals are limited to clearly point-in-time/superseded docs: docs/state-machine-stage2-evidence-recovery-plan.md, docs/SECURITY-AUDIT-SUMMARY.md, and four design/spec HTML analyses. --- artifacts/architecture-design.md | 5 +- docs/SECURITY-AUDIT-SUMMARY.md | 362 ---- docs/design-cli-redesign-analysis.html | 743 -------- docs/design-npm-global-install-fixes.html | 1065 ------------ docs/design-onboarding-improvements.html | 1487 ----------------- ...erminal-port-and-project-id-hardening.html | 573 ------- ...e-machine-stage2-evidence-recovery-plan.md | 347 ---- 7 files changed, 3 insertions(+), 4579 deletions(-) delete mode 100644 docs/SECURITY-AUDIT-SUMMARY.md delete mode 100644 docs/design-cli-redesign-analysis.html delete mode 100644 docs/design-npm-global-install-fixes.html delete mode 100644 docs/design-onboarding-improvements.html delete mode 100644 docs/specs/runtime-terminal-port-and-project-id-hardening.html delete mode 100644 docs/state-machine-stage2-evidence-recovery-plan.md diff --git a/artifacts/architecture-design.md b/artifacts/architecture-design.md index d52104f0e..0f279e403 100644 --- a/artifacts/architecture-design.md +++ b/artifacts/architecture-design.md @@ -32,7 +32,7 @@ Human only intervenes when notified. Everything else is handled. ### Design Principles 1. **Push, not pull**: Notifications are the primary interface. Dashboard is secondary drill-down. -2. **Server-centric**: All agents report to a central server. The server coordinates everything. +2. **Server-centric**: One central daemon (`ao start`) manages every registered project, and all agents report to it. Each project gets its own orchestrator agent — one orchestrator per project, never a single orchestrator spanning all projects. 3. **Plugin everything**: 8 pluggable abstraction slots. Swap any component. 4. **Works out of the box**: Default config (tmux + claude-code + worktree + github) requires zero setup beyond `npx agent-orchestrator init`. 5. **Silence by default, loud when needed**: Auto-handle routine issues (CI failures, review comments). Only notify the human when their judgment or action is truly required. @@ -44,7 +44,8 @@ Human only intervenes when notified. Everything else is handled. | Term | Definition | Examples | | ---------------- | ------------------------------------------ | -------------------------------- | -| **Orchestrator** | The central server that manages everything | The Next.js app | +| **Orchestrator (daemon)** | The central server process that manages **all** registered projects | `ao start` + the Next.js app | +| **Orchestrator agent** | A per-project agent session that spawns and supervises workers — **one per project** | `my-app-orchestrator`, `backend-api-orchestrator` | | **Project** | A configured repository to work on | `my-app`, `backend-api` | | **Session** | A running agent instance working on a task | `my-app-1`, `my-app-2` | | **Runtime** | Where/how the session executes | tmux, docker, k8s, process | diff --git a/docs/SECURITY-AUDIT-SUMMARY.md b/docs/SECURITY-AUDIT-SUMMARY.md deleted file mode 100644 index 3c53e5aa0..000000000 --- a/docs/SECURITY-AUDIT-SUMMARY.md +++ /dev/null @@ -1,362 +0,0 @@ -# Security Audit Summary — Agent Orchestrator - -**Date**: 2026-02-16 -**Auditor**: Claude Sonnet 4.5 -**Scope**: Full codebase + git history secret scanning, automated prevention measures - -## Executive Summary - -✅ **Security audit completed successfully** - -- ⚠️ **1 historical secret found** (OpenClaw token, already removed from current code) -- ✅ **0 secrets in current codebase** -- ✅ **Automated prevention measures implemented** -- ✅ **CI/CD security pipeline added** -- ✅ **Documentation updated with security best practices** - ---- - -## Findings - -### 1. Historical Secret Leak (RESOLVED) - -**Issue**: OpenClaw notifier token found in git history - -- **Token**: `1af5c4f...872` (redacted - visible in commit history) -- **File**: `agent-orchestrator.yaml` -- **Commit**: `0393ab70a83e090883895d2168aa39a76f997ec8` -- **Date**: 2026-02-15 -- **Status**: Token already removed from current code, still in git history - -**Impact**: Medium -**Likelihood**: Low (local development token, not production) - -**Action Required**: - -- ⚠️ If this token is still in use, **rotate it immediately** -- Token is documented in [SECURITY.md](../SECURITY.md) - -### 2. Current Codebase - -**Status**: ✅ **CLEAN** - -Scanned 1.46 MB of code: - -- No hardcoded API keys -- No authentication tokens -- No passwords or private keys -- Test files use dummy values (`test_key`, `https://hooks.slack.com/test`) -- Example configs use environment variable references (`${SLACK_WEBHOOK_URL}`) - ---- - -## Security Measures Implemented - -### 1. Gitleaks Configuration (`.gitleaks.toml`) - -**Purpose**: Prevent accidental commits of secrets - -**Features**: - -- Uses all default gitleaks rules (covers 100+ secret patterns) -- Custom allowlist for false positives -- Ignores build artifacts (`node_modules/`, `dist/`, `.next/`) -- Allowlists test files (dummy secrets are OK) -- Allowlists environment variable references (`${VAR_NAME}`) - -**Patterns Detected**: - -- GitHub tokens (`ghp_*`, `gho_*`, `ghs_*`, `ghu_*`) -- Linear API keys (`lin_api_*`) -- Slack webhooks & tokens (`xoxb-*`, `xoxa-*`, etc.) -- Anthropic API keys (`sk-ant-api03-*`) -- OpenAI API keys (`sk-*`) -- AWS keys (`AKIA*`) -- JWT tokens (`eyJ*`) -- Private keys (`-----BEGIN PRIVATE KEY-----`) -- Database connection strings (`postgres://user:pass@host`) -- Generic API keys (`api_key=...`, `token=...`, `password=...`) - -**Test**: - -```bash -# Scan current files -gitleaks detect --no-git - -# Scan staged files (pre-commit) -gitleaks protect --staged - -# Scan full git history -gitleaks detect -``` - -### 2. Pre-commit Hook (`.husky/pre-commit`) - -**Purpose**: Block commits containing secrets - -**Behavior**: - -- Runs automatically before every `git commit` -- Scans only staged files (fast) -- Provides helpful error messages if secrets detected -- Gracefully skips if gitleaks not installed (with warning) - -**Example Output**: - -```bash -🔒 Scanning staged files for secrets... -✅ No secrets detected -``` - -Or if secret detected: - -```bash -❌ Secret(s) detected in staged files! - -To fix: - 1. Remove the secret from the file - 2. Use environment variables instead: ${SECRET_NAME} - 3. Add to .env.local (which is in .gitignore) - 4. Update agent-orchestrator.yaml.example with placeholder values - -If this is a false positive, update .gitleaks.toml allowlist -``` - -**Setup**: - -- Husky installed as dev dependency -- Hook is executable and version-controlled -- `prepare` script ensures hook is installed on `pnpm install` - -### 3. GitHub Actions Security Workflow (`.github/workflows/security.yml`) - -**Purpose**: Automated security scanning in CI/CD - -**Jobs**: - -1. **Gitleaks** — Scans full git history on every push/PR -2. **Dependency Review** — Scans PRs for vulnerable dependencies -3. **NPM Audit** — Detects known vulnerabilities in dependencies - -**Triggers**: - -- Every push to `main` -- Every pull request to `main` -- Weekly scheduled scan (Monday 8am UTC) - -**Benefits**: - -- Catches secrets missed by pre-commit hook -- Prevents secrets from reaching main branch -- Alerts on dependency vulnerabilities -- Provides security badge for repo - -### 4. Updated `.gitignore` - -**Purpose**: Prevent accidental commits of secret files - -**Added Patterns**: - -```gitignore -# Environment files -.env -.env.local -.env.*.local -.env.production.local -.env.development.local -.env.test.local - -# Credentials and secrets -*.key -*.pem -*.p12 -*.pfx -*.cer -*.crt -*.der -*.csr -secrets.yaml -secrets.yml -credentials.json -credentials.yaml -*-credentials.* -.secrets/ -.credentials/ - -# API keys and tokens -.token -.api-key -*-token.txt -*-api-key.txt - -# Cloud provider credentials -.aws/ -.gcloud/ -.azure/ - -# SSH keys -id_rsa -id_dsa -id_ecdsa -id_ed25519 -*.ppk - -# Local config (may contain secrets) -agent-orchestrator.yaml -``` - -**Critical**: `agent-orchestrator.yaml` is now ignored because it contains user secrets - -### 5. Documentation - -**Created/Updated**: - -1. **[SECURITY.md](../SECURITY.md)** — Security policy & best practices - - Responsible disclosure process - - Historical audit findings - - Developer best practices - - User best practices - - Required secrets table - - Security tools reference - -2. **[README.md](../README.md)** — Added security section - - How secret scanning works - - Link to SECURITY.md - - Environment variable usage examples - - Required secrets table - -3. **[docs/DEVELOPMENT.md](./DEVELOPMENT.md)** — Developer security guide - - Secret scanning during development - - What triggers the scanner - - How to handle false positives - - Environment variable conventions - - Testing locally - -**Key Messages**: - -- ⚠️ **NEVER commit real secrets to git** -- ✅ **Always use environment variables** -- ✅ **Pre-commit hook will block secrets** -- ✅ **CI will catch anything that slips through** - ---- - -## Verification - -### Automated Scans - -```bash -# ✅ Current codebase scan -$ gitleaks detect --no-git -INFO: scanned ~1.46 MB in 79.9ms -INFO: no leaks found - -# ⚠️ Full git history scan -$ gitleaks detect -WARN: leaks found: 1 -Finding: OpenClaw token in commit 0393ab70 (documented) -``` - -### Security Checklist - -- [x] Gitleaks configuration created and tested -- [x] Pre-commit hook installed and working -- [x] GitHub Actions security workflow added -- [x] `.gitignore` updated with secret patterns -- [x] SECURITY.md created with disclosure process -- [x] README.md updated with security section -- [x] Development docs updated with security practices -- [x] All example configs use placeholders (not real secrets) -- [x] Test files use dummy values (not real secrets) -- [x] Documentation clarifies which env vars are required - ---- - -## Recommendations - -### Immediate Actions - -1. **Rotate OpenClaw Token** (if still in use) - - Generate new token - - Update deployment configs - - Revoke old token - -2. **Set Up Required Environment Variables** - - ```bash - # Add to ~/.zshrc or ~/.bashrc - export GITHUB_TOKEN="ghp_..." - export LINEAR_API_KEY="lin_api_..." - export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/..." - ``` - -3. **Verify Pre-commit Hook Works** - ```bash - # Try committing a fake secret (should be blocked) - echo "token=ghp_XXXX_fake_token_for_testing_XXXX" > test.txt - git add test.txt - git commit -m "test" # Should fail with error message - rm test.txt - ``` - -### Ongoing Practices - -1. **Code Review**: Check PRs for hardcoded credentials -2. **Token Rotation**: Rotate tokens every 90 days -3. **Minimal Permissions**: Use read-only tokens when possible -4. **Secret Management**: Consider 1Password, AWS Secrets Manager, etc. -5. **Monitor CI**: Watch for security workflow failures -6. **Update Dependencies**: Keep gitleaks and dependencies up-to-date - -### Before Open-Sourcing - -- [ ] Verify all historical secrets have been rotated -- [ ] Confirm no production secrets in git history -- [ ] Add security badge to README -- [ ] Set up security@composio.dev email alias -- [ ] Enable GitHub security features: - - [ ] Dependabot alerts - - [ ] Code scanning - - [ ] Secret scanning (if available for public repos) - ---- - -## Tools Used - -| Tool | Purpose | Version | -| ----------------------------------------------------- | --------------- | ------- | -| [Gitleaks](https://github.com/gitleaks/gitleaks) | Secret scanning | 8.x | -| [Husky](https://typicode.github.io/husky/) | Git hooks | 9.1.7 | -| [GitHub Actions](https://github.com/features/actions) | CI/CD security | — | - ---- - -## Summary Statistics - -- **Files Scanned**: 1.46 MB -- **Git Commits Scanned**: 404 -- **Historical Secrets Found**: 1 (documented, requires rotation) -- **Current Secrets Found**: 0 -- **False Positives**: 0 (test files allowlisted) -- **Time to Scan**: ~80ms (current), ~960ms (full history) - ---- - -## Conclusion - -✅ **Agent Orchestrator is now protected against secret leaks** - -The codebase is currently clean, with one historical secret that needs rotation. Comprehensive automated scanning prevents future accidents. All developers are protected by pre-commit hooks, and CI/CD ensures nothing reaches the main branch. - -**Next Steps**: - -1. Rotate the OpenClaw token if still in use -2. Test the pre-commit hook locally -3. Monitor CI for security workflow runs -4. Review SECURITY.md before first public release - ---- - -**Audit completed**: 2026-02-16 -**Approved for**: Local development, internal testing -**Before open-sourcing**: Rotate historical secrets, verify no production credentials diff --git a/docs/design-cli-redesign-analysis.html b/docs/design-cli-redesign-analysis.html deleted file mode 100644 index 745cfec89..000000000 --- a/docs/design-cli-redesign-analysis.html +++ /dev/null @@ -1,743 +0,0 @@ - - - - - - Design: CLI Redesign — Simplified Onboarding — Agent Orchestrator - - - - - -
- -

CLI Redesign: Simplified Onboarding

-
- PR #463 - Status: Implemented - Updated: 2026-03-17 -
- - -

1. Overview

- -

- The CLI redesign eliminates multi-step onboarding by collapsing ao init, - ao add-project, and ao start into a single command. - The target experience: -

- -
npm install -g @composio/ao && ao start
- -

- Everything else — config generation, project detection, agent runtime discovery, - and environment validation — happens automatically. When AO is already running, - the CLI detects it and offers contextual options instead of crashing. -

- -
- - -

2. Before vs After

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AspectBeforeAfter
Setup steps3–4 commands: ao init, edit YAML, ao add-project, ao start1 command: ao start
Config creationManual via ao initAuto-generated on first ao start using environment detection
Agent selectionUser edits YAML manuallyAuto-detected from installed runtimes; interactive picker if multiple
Spawning a sessionao spawn <project> <issue>ao spawn <issue> (project auto-detected)
Already-running handlingError / undefined behaviorInteractive menu (human) or structured info+exit (agent)
Config referenceRead source code or docs siteao config-help prints annotated schema
ao initPrimary setup commandDeprecated thin wrapper that calls ao start config path
ao add-projectRequired for each repoFully removed
- -
-
-

Before (4 steps)

-
ao init
-# manually edit agent-orchestrator.yaml
-ao add-project my-app ~/code/my-app
-ao start
-
-
-

After (1 step)

-
ao start
-# config auto-generated, agent auto-detected
-# dashboard opens at http://localhost:3000
-
-
- -
- - -

3. Architecture Changes

- -

File Map

- -
    -
  • packages/cli/src/lib/running-state.ts
  • -
  • packages/cli/src/lib/caller-context.ts
  • -
  • packages/cli/src/lib/detect-agent.ts
  • -
  • packages/cli/src/lib/detect-env.ts
  • -
  • packages/cli/src/lib/config-instruction.ts
  • -
  • packages/cli/src/commands/start.ts
  • -
  • packages/cli/src/commands/spawn.ts
  • -
  • packages/cli/src/commands/init.ts (rewritten as deprecation wrapper)
  • -
  • packages/cli/src/commands/add-project.ts
  • -
  • packages/core/src/orchestrator-prompt.ts
  • -
  • packages/core/src/session-manager.ts (env var injection)
  • -
  • packages/plugin-agent-claude-code (detect + displayName)
  • -
  • packages/plugin-agent-aider (detect + displayName)
  • -
  • packages/plugin-agent-codex (detect + displayName)
  • -
  • packages/plugin-agent-opencode (detect + displayName)
  • -
- -

- The core architecture (dist-server, session manager, workspace plugins) remains unchanged. - Changes are concentrated in the CLI entry layer and the thin coordination between CLI and core. -

- -
- - -

4. New Library Files

- -

running-state.ts — Single-Instance Tracking

-

- Tracks whether an AO instance is already running. Writes a - ~/.agent-orchestrator/running.json file containing the running state: -

- -
interface RunningState {
-  pid: number;
-  configPath: string;
-  port: number;
-  startedAt: string;
-  projects: string[];
-}
- -
    -
  • Uses an advisory lockfile (running.lock) with O_CREAT | O_EXCL for atomic creation — prevents concurrent registration races.
  • -
  • Auto-prunes stale entries: if the recorded PID is dead (process.kill(pid, 0) fails), the entry is cleared on read.
  • -
  • Lock timeout of 5 seconds with force-remove handles crashed processes that left a stale lock.
  • -
  • Exports: register(), unregister(), getRunning(), isAlreadyRunning(), waitForExit(pid, timeoutMs).
  • -
- -

caller-context.ts — Human/Orchestrator/Agent Detection

-

- Detects who is invoking the CLI and provides typed helpers for propagating context. -

- -
type CallerType = "human" | "orchestrator" | "agent";
- -
    -
  • getCallerType(): If AO_CALLER_TYPE env var is set, trusts it directly. Otherwise: TTY = human, non-TTY = agent.
  • -
  • isHumanCaller(): Convenience boolean check.
  • -
  • setCallerContext(env, opts): Injects AO_CALLER_TYPE, AO_SESSION_ID, AO_PROJECT_ID, AO_CONFIG_PATH, and AO_PORT into a spawn environment record.
  • -
- -

detect-agent.ts — Plugin-Based Runtime Discovery

-

- No hardcoded binary paths. Dynamically imports each agent plugin and calls its detect() method. -

- -

Known plugins:

- - - - - - - - -
NamePackage
claude-code@composio/ao-plugin-agent-claude-code
aider@composio/ao-plugin-agent-aider
codex@composio/ao-plugin-agent-codex
opencode@composio/ao-plugin-agent-opencode
- -

Selection logic in detectAgentRuntime():

-
    -
  • 0 agents detected → default to claude-code.
  • -
  • 1 agent → auto-select.
  • -
  • Multiple + human caller → interactive picker via node:readline/promises.
  • -
  • Multiple + non-human → prefer claude-code, else first available.
  • -
- -

detect-env.ts — Environment Detection

-

- Probes the local environment to auto-populate config fields. Returns an EnvironmentInfo object: -

-
    -
  • Git: repo detection, remote URL parsing, owner/repo extraction, current branch, default branch (via detectDefaultBranch()).
  • -
  • Tools: tmux availability, gh CLI availability, gh auth status.
  • -
  • API keys: LINEAR_API_KEY, SLACK_WEBHOOK_URL presence.
  • -
- -

config-instruction.ts — Config Schema Reference

-

- Returns a comprehensive annotated YAML schema covering every config field: - ports, defaults (runtime, agent, workspace, notifiers), project settings (repo, path, branch, - agentConfig, agentRules, workspace symlinks/postCreate, tracker, SCM), - notification channels, and notification routing. Used by ao config-help. -

- -
- - -

5. Command Changes

- -

ao start changed

-

Now the single entry point for all of AO. On first run:

-
    -
  1. Calls detectEnvironment(cwd) to probe git, tools, and APIs.
  2. -
  3. Calls detectAgentRuntime() to find an installed agent.
  4. -
  5. Generates agent-orchestrator.yaml with all detected values.
  6. -
  7. Calls register() to write running state to ~/.agent-orchestrator/running.json.
  8. -
  9. Starts the dist-server and orchestrator session.
  10. -
-

If already running, delegates to the Already-Running Detection flow (Section 8).

-

Also exports createConfigOnly() for the deprecated ao init wrapper.

- -

ao spawn changed

-

- Simplified from ao spawn <project> <issue> to ao spawn [issue]. - The project is always auto-detected — no project argument is accepted. - See Section 9 for the auto-detection logic. -

-

- If a user passes two args (old syntax), a friendly warning is shown: -

-
⚠ 'ao spawn <project> <issue>' is no longer supported.
-  The project is now auto-detected. Use:
-
-    ao spawn INT-100    # spawn with issue INT-100
-    ao spawn            # spawn without an issue
-

- The autoDetectProject() function resolves the project from: single project in config, - AO_PROJECT_ID env var, or cwd matching a project path. -

- -

ao batch-spawn changed

-

Same simplification: takes only issue IDs as arguments. The project is always auto-detected — no project prefix is accepted.

- -

ao init deprecated

-

Reduced to a thin wrapper that prints a deprecation warning then delegates to createConfigOnly() from start.ts:

-
// init.ts — full implementation
-program.command("init")
-  .description("[deprecated] Use 'ao start' instead")
-  .action(async () => {
-    console.log("'ao init' is deprecated. Use 'ao start' instead.");
-    const { createConfigOnly } = await import("./start.js");
-    await createConfigOnly();
-  });
- -

ao add-project removed

-

Fully deleted. Projects are auto-detected from cwd or added manually to the YAML config file.

- -

ao config-help new

-

Prints the annotated config schema from config-instruction.ts to stdout. Provides both humans and orchestrator agents a quick reference without needing external docs.

- -
- - -

6. Plugin System Enhancements

- -

All four agent plugins gained two new exports to support runtime discovery:

- - - - - - - - - - - - - - - - - -
ExportTypePurpose
detect()() => booleanReturns true if the agent runtime is available on the system (binary exists in PATH, required API key is set, etc.)
manifest.displayNamestringHuman-readable name for the interactive picker (e.g. "Claude Code", "Aider", "OpenAI Codex", "OpenCode")
- -

- These are consumed by detect-agent.ts via dynamic import(). - Plugins that fail to import (not installed) are silently skipped — this is by design, - as missing plugins are the normal case. -

- -
- - -

7. Session Environment Variables

- -

- Every spawned session (orchestrator and worker) receives these environment variables, - set in session-manager.ts (core package) at the single spawn point: -

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
VariableValuePurpose
AO_CALLER_TYPE"orchestrator" or "agent"Tells the CLI who is calling — affects interactive prompts and error handling
AO_PROJECT_IDProject key from configEnables ao spawn <issue> auto-detection inside agent sessions
AO_CONFIG_PATHAbsolute path to YAMLSessions find config without filesystem search
AO_PORTDashboard port numberAgents reach the API without parsing config
- -

- The setCallerContext() helper in caller-context.ts provides a typed interface - for populating these variables. All three spawn paths (orchestrator session, ao spawn, ao batch-spawn) - go through the session manager, ensuring consistency. -

- -
- - -

8. Already-Running Detection

- -

- When ao start is invoked and isAlreadyRunning() returns a live state, - behavior branches on caller type: -

- -
- graph TD - A["ao start"] --> B{"isAlreadyRunning()?"} - B -->|No| C["Normal startup"] - B -->|Yes| D{"getCallerType()"} - D -->|human| E["Interactive menu"] - D -->|agent / orchestrator| F["Print JSON state + exit 0"] - E --> G["Open dashboard in browser"] - E --> H["Start new instance — kill old"] - E --> I["Override config + restart"] - E --> J["Quit"] -
- -

- Human callers get an interactive menu with four options: open the existing dashboard, - start a new instance (killing the old), override with a new config, or quit. -

-

- Agent/orchestrator callers receive a structured info dump (port, PID, projects list) - and a clean exit code 0, so they can connect to the already-running instance. -

- -
- - -

9. Spawn Auto-Detection Logic

- -

The ao spawn argument parser uses this decision tree:

- -
- graph TD - A["ao spawn [issue]"] --> B{"Two args provided?"} - B -->|Yes| C["⚠ Warning: old syntax
Show correct usage + exit"] - B -->|No| D{"One arg provided?"} - D -->|No| E["autoDetectProject
no issue — bare session"] - D -->|Yes| F["arg = issueId
autoDetectProject"] -
- -

autoDetectProject() Resolution Order

-
    -
  1. If only one project in config → use it.
  2. -
  3. If AO_PROJECT_ID env var is set and matches a configured project → use it.
  4. -
  5. If process.cwd() matches a project's path field → use it.
  6. -
  7. Otherwise → throw error listing available projects.
  8. -
- -

This means an orchestrator agent calling ao spawn INT-1234 from within a session always resolves correctly via the injected AO_PROJECT_ID env var.

- -
- - -

10. Testing Scenarios

- - - - - - - - - - - - - - - - - - - - - - - - - -
#ScenarioExpected Outcome
1Fresh install, ao start in a git repoConfig auto-generated with detected repo, agent, branch. Server starts.
2Fresh install, ao start outside git repoError with clear message: "Run ao start inside a git repository."
3ao start when already running (human)Interactive menu: open / restart / override / quit.
4ao start when already running (agent)JSON state printed to stdout, exit 0.
5ao start with stale PID in running.jsonStale entry auto-pruned, fresh start proceeds normally.
6ao initDeprecation warning printed, config created via createConfigOnly().
7ao spawn #123 (single project in config)Auto-detects the only project, spawns session for issue #123.
8ao spawn my-app #123 (two args)Warning: "'ao spawn <project> <issue>' is no longer supported." Shows correct usage and exits.
9ao spawn #123 (multi-project, cwd matches one)Auto-detects project from cwd path match.
10ao spawn #123 (multi-project, no cwd match)Error listing available projects.
11ao spawn my-app (arg matches a project ID)Treated as issue "my-app" (project always auto-detected). May fail if no project matches cwd/env.
12ao spawn (no args, single project)Auto-detect project, no issue — spawns bare session.
13ao batch-spawn #1 #2 #3Auto-detect project, spawn 3 sessions with duplicate detection.
14ao batch-spawn #1 #2 #3 (multi-project, AO_PROJECT_ID set)Env var resolves project. Spawn 3 sessions.
15ao config-helpFull annotated YAML schema printed to stdout.
16Agent session calls ao spawn #123 with AO_PROJECT_ID setEnv var resolves project correctly without cwd match.
17Multiple agents installed, human runs ao startInteractive picker with displayName labels shown.
18Multiple agents installed, non-TTY ao startClaude Code auto-selected as preferred default.
- -
- - -

11. Design Decisions

- -
- priority field removed from spawn. - Spawning inherits priority from the project config. Per-spawn priority added API surface with no demonstrated use case — callers can override via config if needed. -
- -
- Config instruction NOT injected into orchestrator prompt. - The orchestrator system prompt focuses on session management and coordination. The full config schema (100+ lines of annotated YAML) would bloat the prompt. Instead, the orchestrator can run ao config-help on demand when it needs to modify configuration. -
- -
- dist-server architecture kept unchanged. - The server, session manager, and workspace plugins are out of scope for this PR. The redesign only touches CLI entry points and the thin coordination layer between CLI and core. -
- -
- add-project fully deleted, not deprecated. - Unlike ao init (which gets a deprecation wrapper for existing users), add-project was never on main and had no production users. Clean deletion avoids dead code and import graph bloat. -
- -
- Env vars set in session-manager.ts (core), not spawn.ts (cli). - All three spawn points (orchestrator session, ao spawn, ao batch-spawn) funnel through the session manager. Setting env vars there provides a single source of truth, eliminating the risk of one spawn path forgetting to inject context. -
- -
- - -

12. Review Findings & Fixes

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CategoryFindingResolution
Lockfile spinBusy-wait loop in acquireLock() blocks the event loop for up to 50ms per iterationAcceptable for a single-writer scenario with sub-50ms contention windows. Async alternative adds complexity with no practical benefit.
Stale lockIf a process crashes between lock acquire and release, the lockfile persists indefinitely5-second timeout triggers force-remove. Second attempt uses O_EXCL to ensure atomicity.
Race conditionTwo concurrent ao start invocations could both see "not running"O_EXCL on lockfile makes register() atomic — the second writer wins and overwrites. First instance detects the conflict on next state check.
Plugin import errorsdetect-agent.ts silently swallows all import errors, including real bugsBy design: missing plugins are the expected case for most users. Import failures (syntax errors in installed plugins) are rare and would surface when that plugin is actually used.
TTY detection edgeprocess.stdout.isTTY is undefined (not false) when pipedThe ternary isTTY ? "human" : "agent" correctly treats undefined as falsy.
ID collisionIf a project ID and an issue ID are identical (e.g. project named "123"), ao spawn 123 treats it as an issueAll single args are now treated as issue IDs. Project is always auto-detected. No ambiguity possible since project ID is never accepted as a positional arg.
Schema driftconfig-instruction.ts returns a static string that can drift from the actual config type definitionsAccepted tradeoff: hand-written annotated comments are significantly more useful than auto-generated JSON Schema output. Drift risk is low given the schema changes infrequently.
Env duplicationEnv vars were initially set in three CLI commands independently, risking divergenceMoved to session-manager.ts in core. All spawn paths use a single setCallerContext() call site.
- -
- - diff --git a/docs/design-npm-global-install-fixes.html b/docs/design-npm-global-install-fixes.html deleted file mode 100644 index 4ddc0bb1e..000000000 --- a/docs/design-npm-global-install-fixes.html +++ /dev/null @@ -1,1065 +0,0 @@ - - - - - - Design: True Two-Step Setup Fixes — Agent Orchestrator - - - -
- -

True Two-Step Setup Fixes

-
- PR #638 - Issues: #619, #624 - Status: Open - Date: 2026-03-24 -
- - -

1. Problem Statement

- -

- The target onboarding experience for Agent Orchestrator is truly two commands - from a fresh machine: -

- -
npm install -g @composio/ao && ao start
- -

- This does not work. Multiple bugs cause ao start to fail after - a global npm install. Beyond the bugs, there is an architectural issue: ao start - hardcodes runtime: "tmux" in the generated config, making tmux a hidden - prerequisite that breaks the "two commands" promise. -

- -

The failure chain on a fresh machine:

- -
    -
  1. "Dependencies not installed"checkBuilt() hardcoded path fails for npm hoisted layout
  2. -
  3. "posix_spawnp: Operation not permitted" — node-pty spawn-helper missing execute bit
  4. -
  5. tmux is a hidden prerequisite — config always sets runtime: "tmux", orchestrator creation fails hard if tmux is missing, kills the dashboard
  6. -
  7. Terminal server crash is permanent — no auto-restart after crash
  8. -
- -

Two-Command Setup Workflow

- - -
- -
npm install -g @composio/ao
-
-
postinstall.js runs
-
-
find node-pty via walk-up resolver
-
-
chmod spawn-helper (+x)
non-fatal on failure
-
-
Install complete
-
- - -
- -
ao start
normal | URL | existing config
-
-
load/create config
defaults.runtime = "tmux"
-
-
runStartup()
-
- - -
tmux -V — installed?
-
-
-
YES
-
-
continue ↓
-
-
-
NO
-
-
askYesNo("Install tmux?")
-
-
- - -
-
-
-
YES
-
-
tryInstallWithAttempts()
logs each cmd before running
-
-
verify tmux -V
-
-
-
OK
-
-
continue ↓
-
-
-
FAIL
-
-
print hints
exit 1
-
-
-
-
-
NO
-
-
print platform install hints
exit 1
-
-
-
- -
-
-
preflight.checkBuilt()
findPackageUp(@composio/ao-core)
-
-
start dashboard
Next.js + terminal servers (auto-restart)
-
-
create orchestrator session
-
-
open dashboard URL
-
-
SUCCESS
-
- -
- - -

2. Bug Analysis

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#BugSeverityIssueAffects
1checkBuilt() hardcoded single-level pathP0#619All npm/yarn global installs
2node-pty spawn-helper missing execute bitP0#624macOS + Linux npm installs
3tmux is a hidden prerequisite — hardcoded runtime: "tmux", no auto-install, no fallbackP0Any fresh machine without tmux
4Terminal server crash is permanent (no restart), bookkeeping bug on restartP1Related to #489linux-arm64 (AWS Graviton)
- -
- - -

3. Fix 1 — checkBuilt() Walk-Up Resolution

- -

Root Cause

-

- preflight.ts verifies @composio/ao-core exists using a single hardcoded path: -

- -
// BEFORE: only checks one level deep
-const nodeModules = resolve(webDir, "node_modules", "@composio", "ao-core");
- -

- npm hoists dependencies to a parent node_modules/. - The check always fails for global installs because ao-core lives one or more - levels up from ao-web. -

- -
-
-

pnpm layout (works)

-
packages/web/
-  node_modules/
-    @composio/ao-core -> ../../core
-
-
-

npm global layout (broken)

-
/usr/lib/node_modules/@composio/
-  ao-web/          # webDir
-  ao-core/         # sibling, not child
-
-
- -

Fix

-

- Replace with findPackageUp() — walks up directories checking - node_modules/@composio/ao-core at each level, mirroring Node's own resolution: -

- -
function findPackageUp(startDir: string, ...segments: string[]): string | null {
-  let dir = resolve(startDir);
-  while (true) {
-    const candidate = resolve(dir, "node_modules", ...segments);
-    if (existsSync(candidate)) return candidate;
-    const parent = dirname(dir);
-    if (parent === dir) break;
-    dir = parent;
-  }
-  return null;
-}
- -

Error messages distinguish install method:

-
    -
  • Path contains node_modules"npm install -g @composio/ao@latest"
  • -
  • Otherwise → "pnpm install && pnpm build"
  • -
- -
- - -

4. Fix 2 — node-pty spawn-helper Postinstall

- -

Root Cause

-

- node-pty@1.1.0 ships spawn-helper without the execute bit. - The monorepo's scripts/rebuild-node-pty.js fixes this via node-gyp rebuild, - but it hardcodes pnpm paths and never runs for npm global installs. - Upstream fix (microsoft/node-pty#866) - is only in 1.2.0-beta. -

- -

Fix

-

- New packages/ao/bin/postinstall.js runs after npm install -g @composio/ao. - Uses the same findPackageUp pattern to locate node-pty, then chmod 0o755 - on spawn-helper. Skips on Windows, silent on failure. -

- -
- Temporary workaround. Remove when node-pty ships stable release with the upstream fix. -
- -
- - -

5. Fix 3 — Startup Preflight: tmux + Interactive Prerequisites

- -

Root Cause

-

- This was the biggest gap in the "two-step" promise. Three compounding issues: -

-
    -
  • Config generation hardcodes runtime: "tmux"
  • -
  • Orchestrator creation fails hard if tmux runtime fails, then kills the dashboard
  • -
  • tmux checks were not enforced consistently across all ao start entry paths (URL start and existing-config retries could bypass friendly remediation)
  • -
- -

On a fresh machine without tmux, the user saw:

-
$ ao start
-✓ Config created: agent-orchestrator.yaml
-⚠ tmux not found — install with: brew install tmux
-...
-✗ Failed to setup orchestrator: tmux: command not found
- -

Fix: Centralized Startup Gate with User Consent

- -
-
ao start
normal | URL | existing config
-
-
runStartup()
-
-
ensureTmux()
-
-
tmux -V — installed?
-
-
-
YES
-
-
continue startup
-
-
-
NO
-
-
askYesNo("Install?")
-
-
-
YES
-
-
tryInstallWithAttempts()
logs each cmd
-
-
verify?
-
-
-
OK
-
-
continue
-
-
-
FAIL
-
-
exit 1
-
-
-
-
-
NO
-
-
print hints
exit 1
-
-
-
-
-
- -

Two-Layer Install Architecture

-

- Install logic lives in two layers, each with a clear responsibility: -

-
    -
  • preflight.checkTmux() — low-level check-only guard used by - ao spawn preflight. It validates tmux -V and throws platform-specific - manual install instructions when missing (no install attempts).
  • -
  • ensureTmux() in start.ts — interactive wrapper. Uses askYesNo() - to get user consent before running tryInstallWithAttempts() with - tmuxInstallAttempts(). Matches the existing ensureGit() pattern. - Non-interactive callers default to skip install and print manual hints.
  • -
- -
// start.ts — interactive install with user consent
-async function ensureTmux(): Promise<void> {
-  const hasTmux = (await execSilent("tmux", ["-V"])) !== null;
-  if (hasTmux) return;
-
-  console.log("⚠ tmux is required for runtime \"tmux\".");
-  const shouldInstall = await askYesNo("Install tmux now?", true, false);
-  if (shouldInstall) {
-    const installed = await tryInstallWithAttempts(
-      tmuxInstallAttempts(),
-      async () => (await execSilent("tmux", ["-V"])) !== null,
-    );
-    if (installed) return; // ✓ tmux installed successfully
-  }
-
-  // Print platform-specific hints and exit
-  for (const hint of tmuxInstallHints()) console.log(hint);
-  process.exit(1);
-}
- -

All-Path Enforcement

-

- start.ts centralizes tmux enforcement in runStartup(), so all - start entry paths are covered (normal start, URL start, and retries with existing config): -

- -
async function runStartup(...) {
-  const runtime = config.defaults?.runtime ?? "tmux";
-  if (runtime === "tmux") {
-    await ensureTmux();
-  }
-  // continue dashboard + orchestrator startup...
-}
- -

Interactive Prerequisite Prompts (TTY)

-

- When ao start detects a missing prerequisite on a fresh machine, it prompts - the user before attempting any install. This ensures no surprises — no silent - sudo, no unexpected package manager invocations. -

- -

Prerequisite Matrix

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PrerequisiteRequired?PromptInstall methods (by platform)On failure
tmuxYes (for runtime: "tmux")Install tmux now? [Y/n] - macOS: brew install tmux
- Linux: apt-getdnf -
blocks — prints platform hints + exit 1
gitYes (for URL clone & add-project)Install Git now? [Y/n] - macOS: brew install git
- Linux: apt-getdnf
- Windows: winget install Git.Git -
blocks — prints platform hints + exit 1
Agent runtime
(Claude Code, Codex, Aider, OpenCode)
Yes (at least one)Numbered menu:
Choose runtime to install [1-5]
- 1. Claude Code (npm i -g @anthropic-ai/claude-code)
- 2. Codex (npm i -g @openai/codex)
- 3. Aider (pipx install aider-chat)
- 4. OpenCode (go install ...)
- 5. Skip -
Config generation continues — user can install later
GitHub CLI (gh)No (optional)Install GitHub CLI now? [y/N]
(defaults to no)
- macOS: brew install gh
- Linux: apt-getdnf
- Windows: winget install GitHub.cli -
Skipped silently — optional dependency
DockerNoNot prompted(no runtime-docker plugin registered)
- -

Key Behaviors

-
    -
  • Always asks first — uses askYesNo() before running any install command. No silent sudo or background package manager invocations.
  • -
  • Logs each command — shows "Running: brew install tmux" (via tryInstallWithAttempts()) so the user knows exactly what's executing.
  • -
  • Blocks on required failures — for tmux and git, prints platform-specific manual install instructions and exits rather than silently degrading the experience.
  • -
  • Non-interactive fallback — when not a TTY (CI, piped scripts, agent callers), canPromptForInstall() returns false and askYesNo() returns its non-interactive default without prompting. Required and optional tools both default to false (skip), then print manual instructions when required tools are missing.
  • -
  • Consistent pattern — all prerequisite helpers (ensureTmux(), ensureGit(), promptInstallAgentRuntime()) follow the same structure: detect → prompt → install → verify → block or skip.
  • -
- -

Example: Fresh Machine Flow

-
$ ao start
-  Detecting environment...
-  ✓ Agent runtime: claude-code
-  ✓ Config created: agent-orchestrator.yaml
-  ⚠ tmux not found — will prompt to install at startup
-
-  ⚠ tmux is required for runtime "tmux".
-  Install tmux now? [Y/n]: y
-    Running: brew install tmux
-  ✓ tmux installed successfully
-
-  Starting dashboard on port 3000...
-  ✓ Dashboard: http://localhost:3000
- -

If the user declines or install fails:

-
  Install tmux now? [Y/n]: n
-
-  ✗ tmux is required but is not installed.
-
-  Install tmux manually, then re-run ao start:
-
-    brew install tmux          # macOS
-    sudo apt install tmux      # Debian/Ubuntu
-    sudo dnf install tmux      # Fedora/RHEL
- -
- We chose to block and show clear instructions rather than fall back to runtime: "process". - The process runtime lacks session attach, terminal panel, and message sending — core AO - features. A degraded first impression is worse than a one-line install command that gives the - full experience. -
- -
- - -

6. Fix 4 — Terminal Server Crash Recovery

- -

Root Cause

-

- start-all.ts spawns three child processes. If a terminal server crashes, - it logs the exit and never restarts. -

- -

Fix

-

- Auto-restart logic with correct bookkeeping: -

- -
    -
  • Terminal servers restart up to 3 times on non-zero exit
  • -
  • Next.js is not restarted
  • -
  • No restart during graceful shutdown
  • -
- -

Bookkeeping: Slot-Based Child Tracking

-

- The initial version had a bug: launch() always called children.push(child), - creating duplicate entries on restart. The cleanup() function counts - children.length and waits for exits, so stale handles would skew the alive count - and trigger the 5-second force-shutdown timer. -

- -

Fix: each process gets a slot index assigned on first launch. Restarts replace in-place:

- -
let slotIndex = -1;
-
-function launch(): ChildProcess {
-  const child = spawn(command, args, { ... });
-
-  child.on("exit", (code) => {
-    if (!shuttingDown && opts?.restart && code !== 0 && restarts < maxRestarts) {
-      restarts++;
-      const replacement = launch();
-      children[slotIndex] = replacement; // replace in-place
-    }
-  });
-
-  // Only push on first launch; restarts replace the existing slot
-  if (slotIndex === -1) {
-    slotIndex = children.length;
-    children.push(child);
-  }
-
-  return child;
-}
- -
- - -

7. Files Changed

- -
    -
  • packages/cli/src/lib/preflight.ts — walk-up resolver, check-only tmux preflight + platform messages
  • -
  • packages/cli/src/commands/start.ts — centralized ensureTmux() gate + interactive install prompts for git/tmux/agent runtimes (+ optional gh)
  • -
  • packages/cli/src/lib/detect-agent.ts — shared runtime selection with optional pre-detected list (avoids redundant plugin scan)
  • -
  • packages/ao/bin/postinstall.js — node-pty spawn-helper chmod
  • -
  • packages/ao/package.json — postinstall script registration
  • -
  • packages/web/server/start-all.ts — restart logic with slot-based bookkeeping
  • -
  • eslint.config.js — packages/ao/bin globals override
  • -
  • packages/cli/__tests__/lib/preflight.test.ts — expanded tests
  • -
- -
- - -

8. What Was Already Working

- -

Investigated but not broken:

- - - - - - - - - - - - - - - - - - - - - - - -
Suspected BlockerActual State
Port 3000 conflictAlready fixedfindFreePort(port + 1) auto-increments up to 100 ports
node-pty crash kills dashboardAlready handleddirect-terminal-ws.ts catches import failure, gracefully rejects connections
findWebDir() in global installAlready handled — uses require.resolve() with proper walk-up
concurrently at runtimeAlready fixed — production uses node dist-server/start-all.js
- -
- - -

9. Post-Merge: True Two-Step Matrix

- -

After this PR, npm install -g @composio/ao && ao start:

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Platformtmux Availabletmux Not Available
macOS (Intel + Apple Silicon)WorksInteractive prompt + brew install
Clear error if brew missing or declined
Linux x64WorksInteractive prompt + apt/dnf install
Clear error if sudo unavailable or declined
Linux arm64 (Graviton)Works
Direct terminal unavailable (no node-pty prebuild)
Interactive prompt + apt/dnf install
Clear error if sudo unavailable or declined
WindowsN/AClear error
Guides user to WSL
- -

- Key improvement: Missing prerequisites are handled with explicit user consent in - ao start. If install is declined or fails, the CLI prints one-line commands to run - manually, then exits clearly. -

- -
- - -

10. Testing

- - - - - - - - - - - -
CheckResult
TypeScript typecheckPasses (CLI + web server)
ESLintNo new errors on changed files
Preflight tests15/15 pass (expanded from 13)
CLI tests222/223 pass (1 = pre-existing flaky doctor-script.test.ts)
- -
- Local validation caveat: if node_modules contains root-owned artifacts - (notably node-pty/build/* from earlier sudo installs), pnpm install - can fail with EACCES on unlink/rebuild. This is an environment ownership issue, - not a behavior regression in startup logic. -
- -

New/Updated Test Cases

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TestWhat It Verifies
pnpm layout (direct match)findPackageUp finds ao-core in webDir/node_modules on first check
npm hoisted layout (walk-up)findPackageUp misses first level, finds ao-core one level up
npm hint on missing depsGlobal install path shows npm install -g hint
pnpm hint on missing depsMonorepo path shows pnpm install && pnpm build hint
Missing distao-core found but dist/index.js missing shows build hint
tmux interactive install successWhen tmux -V fails initially and user-approved install succeeds, ensureTmux() passes
tmux interactive install declined/failureWhen user declines or install attempts fail, prints platform-specific instructions and exits
- -
- - -

11. Design Decisions

- -
- Prompted tmux install in ao start, then block with clear instructions on failure. - We ask first, then try brew (macOS) and apt/dnf (Linux) only after consent. If install is - declined or fails, we exit with a clear one-line install command rather than falling back to runtime: "process". - The process runtime lacks session attach, terminal panel, and ao send — - a degraded first impression is worse than asking the user to run one command. -
- -
- Slot-based restart bookkeeping. - Each child process gets a fixed slot index in the children array on first launch. - Restarts replace in-place (children[slotIndex] = replacement) instead of pushing, - preventing duplicate entries that would break the cleanup countdown. -
- -
- Postinstall is temporary. - The spawn-helper chmod workaround will be removed once node-pty ships - a stable release with the upstream fix (microsoft/node-pty#866). -
- -
- - diff --git a/docs/design-onboarding-improvements.html b/docs/design-onboarding-improvements.html deleted file mode 100644 index bb5678826..000000000 --- a/docs/design-onboarding-improvements.html +++ /dev/null @@ -1,1487 +0,0 @@ - - - - - - Design: Onboarding Friction Reduction — Agent Orchestrator - - - -
-

Design: Onboarding Friction Reduction

-
- PR: feat/onboarding-improvements - Author: Suraj + Claude - Date: 2026-03-13 -
- -

Motivation

-

New users hitting Agent Orchestrator for the first time faced multiple friction points that could cause them to abandon setup before ever spawning an agent. The goal of this PR is to make the path from npm install or git clone to ao spawn as smooth as possible — ideally zero interruptions. Fixes cover both the npm install path and the source/contributor path.

- -

Problems Identified

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#ProblemSeverityWho it affects
1setup.sh crashes with EACCES on npm linkBlockingEvery macOS user
2ao init asks 13 prompts before generating configHigh frictionEvery new user
3Adding a second project requires editing YAML by handHigh frictionMulti-project users
4ao start crashes if configured port is busyMedium frictionUsers running other dev servers
5--smart flag shows "coming soon" placeholderLow frictionUsers exploring CLI flags
6Same npm link bug in ao update and ao doctor --fixBlockingUsers updating or diagnosing
7No "what's next" guidance — user doesn't know what to run or whereMedium frictionEvery new user
8ao start crashes for npm users — @composio/ao-web (dashboard) was never publishedBlockingEvery npm user
9npm install -g fails with EACCES on default macOS Node — no guidance providedHigh frictionnpm users on macOS
10Dashboard production server uses npx next start — slow global lookup, may download Next.js at runtimeMedium frictionnpm users
11findWebDir() returns a nonexistent path instead of throwing — error message says "Run: pnpm install" (wrong for npm users)Medium frictionnpm users when package resolution fails
12setup.sh / ao-update.sh — non-interactive npm link failure exits 0 (CI sees green, ao not linked)Medium frictionCI pipelines
13detectDefaultBranch duplicated in init.ts and add-project.ts — bug fixes won't propagateLow frictionMaintainers
- -

Changes

- -

1. Fix npm link permission error

-
-
-

Before

-

npm link fails with EACCES on macOS. Script exits. User is stuck with no ao command and no guidance.

-
-
-

After

-

Tries npm link first. If it fails and terminal is interactive, auto-retries with sudo. If non-interactive (CI), prints the manual command and exits with code 1.

-
-
-
-
npm link retry logic
-
Run npm link
-
-
Success?
-
-
-
-
YES
-
-
Done ✓
-
-
-
NO (EACCES)
-
-
Interactive terminal?
-
-
-
-
YES
-
-
Retry with sudo npm link
-
-
Done ✓
-
-
-
NO (CI)
-
-
Print manual fix + exit 1
-
-
-
-
-
-
    -
  • scripts/setup.sh
  • -
  • scripts/ao-update.sh
  • -
  • scripts/ao-doctor.sh
  • -
- -

2. Make ao init default to auto-detection

-
-
-

Before

-

ao init → 13 interactive prompts
ao init --auto → zero prompts

-

Most users should use --auto but it wasn't the default.

-
-
-

After

-

ao init → auto-detects everything, zero prompts
ao init --interactive → full wizard for manual control

-
-
-
-
ao init decision flow
-
User runs ao init
-
-
--interactive flag?
-
-
-
-
NO (default)
-
-
Auto-detect git remote, branch, language, framework
-
-
Generate config + agent rules from templates
-
-
Write agent-orchestrator.yaml
-
-
-
YES
-
-
13-prompt interactive wizard
-
-
User manually configures each setting
-
-
Write agent-orchestrator.yaml
-
-
-
-
    -
  • packages/cli/src/commands/init.ts
  • -
- -

3. Add ao add-project <path> command

-
-
-

Before

-

Adding a project meant opening agent-orchestrator.yaml and manually writing YAML with the correct structure, session prefix, and repo format.

-
-
-

After

-

One command: ao add-project ~/Desktop/mono

-
-
-

The command automatically:

-
    -
  1. Resolves the path and finds the existing config
  2. -
  3. Detects git remote (owner/repo)
  4. -
  5. Detects default branch (main/master/etc.)
  6. -
  7. Generates a unique session prefix (validates against existing ones)
  8. -
  9. Detects project type (language, framework, test runner)
  10. -
  11. Generates agent rules from templates
  12. -
  13. Appends the project to the config
  14. -
-
-
ao add-project pipeline
-
Resolve
path
-
-
Find existing
config
-
-
Detect git
remote
-
-
Detect default
branch
-
-
Generate unique
session prefix
-
-
Detect project
type
-
-
Generate
agent rules
-
-
Append to
config
-
-
    -
  • packages/cli/src/commands/add-project.ts
  • -
  • packages/cli/src/index.ts
  • -
- -

4. Auto-find free port on ao start

-
-
-

Before

-
Port 3000 is already in use.
-Free it or change 'port' in agent-orchestrator.yaml.
-
-
-

After

-
Port 3000 is busy — using 3001 instead.
-
-
-
-
ao start — port resolution
-
Read port from config (default: 3000)
-
-
Port available?
-
-
-
-
YES
-
-
Start on configured port
-
-
-
NO
-
-
Scan port+1 … port+20
-
-
Free port found?
-
-
-
-
YES
-
-
Start on free port
Log: "Port X busy — using Y"
-
-
-
NO
-
-
Error: no free port in range
-
-
-
-
-
-
    -
  • packages/cli/src/commands/start.ts
  • -
- -

5. Remove --smart placeholder

-
-
-

Before

-

ao init --auto --smart showed "AI-powered rule generation not yet implemented". A dead feature flag.

-
-
-

After

-

Removed entirely. Template-based rules are the default and work well.

-
-
-
    -
  • packages/cli/src/commands/init.ts
  • -
- -

6. Contextual "next step" hints with directory guidance

-
-
-

Before

-

After each command completes, user gets either no guidance or a generic message like "Next: ao start". No indication of where to run the next command — users frequently ran ao init inside the agent-orchestrator repo instead of their project.

-
-
-

After

-

Every command prints a clear "what's next" block that tells the user exactly what to run and from which directory. Each step chains into the next, creating a guided path through the entire flow.

-
-
- -
-
Guided handoff chain — each command tells you the next
-
setup.sh completes
-
-
- Prints:
- cd ~/your-project
- ao init -
-
-
ao init completes
-
-
- Prints: (from this directory: ~/your-project)
- ao start
- ao add-project ~/other-repo -
-
-
ao start completes
-
-
- Prints:
- ao spawn my-project <issue-number> -
-
-
User is productive ✓
-
- -

Example output after setup.sh:

-
Setup complete!
-
-What's next:
-
-  Navigate to your project directory and initialize:
-
-    cd ~/your-project
-    ao init            # auto-detects everything, zero prompts
-
-  Then start the orchestrator + dashboard:
-
-    ao start            # run this from your project directory
-
-  Want to add more projects later?
-
-    ao add-project ~/path/to/another-repo
- -
    -
  • scripts/setup.sh
  • -
  • packages/cli/src/commands/init.ts
  • -
  • packages/cli/src/commands/start.ts
  • -
  • packages/cli/src/commands/add-project.ts
  • -
- -

7. Publish @composio/ao-web dashboard on npm

- -

This was the single biggest blocker for npm users. Every other AO package was published on npm, but the dashboard — the package that ao start needs to launch the web UI — was never published. npm users hit an immediate fatal crash.

- -

Root cause: 5 blockers preventing publishing

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#BlockerWhy it existedFix
1"private": true in package.jsonDashboard was never intended as a standalone npm packageRemoved the flag
2In changeset ignore listExcluded from versioning/publishing pipelineMoved to linked array so it versions with other packages
3Dev-only server (pnpm run dev via concurrently)Used tsx watch for TypeScript — doesn't work outside monorepoCreated production entry point (start-all.ts → compiled to dist-server/)
4node-pty as hard dependencyNative C++ addon — fails to compile without build tools (Python, make, gcc)Moved to optionalDependencies with dynamic import + graceful fallback
5Cross-boundary importsserver/terminal-observability.ts imported from ../src/lib/ — breaks when compiledMoved resolveProjectIdForSessionId to @composio/ao-core
- -

What the user experienced

-
-
-

Before

-
$ npm install -g @composio/agent-orchestrator
-$ ao start
-Error: Could not find @composio/ao-web package.
-Run: pnpm install
-

Fatal crash. The suggested fix (pnpm install) doesn't work for npm users. Complete dead end — dashboard was simply not available outside the monorepo.

-
-
-

After

-
$ npm install -g @composio/agent-orchestrator
-$ ao start
-✓ Dashboard starting on http://localhost:3000
-✓ Lifecycle worker started
-✓ Orchestrator session created
-
-Next step:
-  ao spawn my-project <issue-number>
-
-
- -

Package size optimization

-

The initial npm pack of @composio/ao-web was 67 MB — Next.js dumps build cache, trace files, and source maps into .next/. We optimized the files field to include only what's needed at runtime:

- -
-
npm package size — @composio/ao-web
-
-
-
Naive "files": [".next"]
-
67 MB
-
67 MB
-
-
-
Optimized files field
-
1 MB
-
1 MB
-
-
-
- -

The optimized files field includes only:

-
"files": [
-  ".next/server",          // compiled server pages + API routes
-  ".next/static",          // client-side JS/CSS bundles
-  ".next/*.json",          // build manifest, routes manifest
-  ".next/BUILD_ID",        // build identifier
-  "dist-server",           // compiled terminal WebSocket servers
-  "next.config.js"         // Next.js configuration
-]
-

Excluded: .next/cache/ (build cache, 60+ MB), .next/trace (webpack trace files), source TypeScript files.

- -

Production architecture

-

In the monorepo, the dashboard runs via concurrently with tsx watch (hot reloading). On npm, we need pre-compiled servers. start-all.ts is the production entry point that spawns 3 processes:

- -
-
start-all.js — production process tree
-
node dist-server/start-all.js
-
-
Parent process (manages lifecycle + graceful shutdown)
-
-
-
-
Next.js
node_modules/.bin/next start
Dashboard UI on PORT
-
-
-
Terminal WS
terminal-websocket.js
ttyd proxy on TERMINAL_PORT
-
-
-
Direct Terminal
direct-terminal-ws.js
node-pty on DIRECT_TERMINAL_PORT
-
-
-
- -

How the CLI decides: dev vs production

-

The CLI needs to work in both the monorepo (for contributors) and as an npm package (for users). It detects the mode by checking whether the server/ source directory exists — present in the monorepo, absent in the published npm package (only dist-server/ ships).

- -
-
ao start — dev vs production detection
-
ao start
-
-
Locate @composio/ao-web via require.resolve or monorepo fallback
-
-
server/ source dir exists?
-
-
-
-
YES (monorepo/source)
-
-
pnpm run dev
tsx watch + HMR + concurrently
-
-
Dev mode ✓
-
-
-
NO (npm package)
-
-
node dist-server/start-all.js
pre-compiled, no dev deps needed
-
-
Production mode ✓
-
-
-
- -

End-to-end npm user journey

-
-
What happens when an npm user runs ao start
-
npm install -g @composio/agent-orchestrator
-
-
npm installs @composio/ao-cli + all plugin deps + @composio/ao-web
workspace:* auto-converted to real versions by pnpm publish
-
-
ao start https://github.com/org/repo
-
-
Clone repo → auto-generate config → find free port
-
-
findWebDir() resolves @composio/ao-web from node_modules
-
-
server/ exists?
-
-
NO → production mode → node dist-server/start-all.js
-
-
Spawns Next.js + terminal servers + orchestrator tmux session
-
-
Dashboard live on http://localhost:3000 ✓
-
- -

Impact

-
-
-
Fatal crash
-
-
Works
-
ao start for npm users
-
-
-
67 MB
-
-
1 MB
-
npm package size
-
-
-
0
-
-
2
-
Startup modes (dev + production)
-
-
- -
    -
  • packages/web/package.json — removed private: true, added files field, made node-pty optional
  • -
  • packages/web/server/start-all.ts — production entry point (spawns 3 processes)
  • -
  • packages/web/tsconfig.server.json — server compilation config
  • -
  • packages/web/next.config.js — added all 8 workspace deps to transpilePackages
  • -
  • packages/web/server/direct-terminal-ws.ts — dynamic node-pty import with graceful fallback
  • -
  • packages/cli/src/commands/start.ts — dev vs production mode detection
  • -
  • packages/cli/src/lib/web-dir.ts — findWebDir() throws with install-specific guidance
  • -
  • packages/cli/package.json — added @composio/ao-web dependency
  • -
  • .changeset/config.json — removed from ignore, added to linked
  • -
  • packages/core/src/utils.ts — moved resolveProjectIdForSessionId here
  • -
  • packages/web/server/terminal-observability.ts — updated import to use @composio/ao-core
  • -
  • packages/web/src/lib/observability.ts — updated import to use @composio/ao-core
  • -
- -

8. Add EACCES permission guidance for npm users

-
-
-

Before

-

npm install -g @composio/agent-orchestrator fails with EACCES on macOS default Node installs. User gets a wall of error text with no actionable fix.

-
-
-

After

-

README and SETUP.md document 3 options: sudo npm install -g (quick fix), npx @composio/agent-orchestrator (no install), or fix the npm prefix directory (permanent). npm is now listed as Option A (recommended) in README.

-
-
-
    -
  • README.md — npm is Option A, added EACCES fix + npx alternative
  • -
  • SETUP.md — added EACCES section with 3 options
  • -
- -

9. Use local Next.js binary in production

-
-
-

Before

-

start-all.ts launched the dashboard with npx next start. npx does a global PATH lookup and, if next isn't found, tries to download it at runtime — adding 30+ seconds or failing on slow/offline networks.

-
-
-

After

-

Uses the local binary directly: node_modules/.bin/next. Instant startup, no network dependency.

-
-
-
    -
  • packages/web/server/start-all.ts
  • -
- -

10. Fix findWebDir() error handling

-
-
-

Before

-

findWebDir() silently returned a nonexistent path when all resolution methods failed. The caller then showed "Could not find @composio/ao-web. Run: pnpm install" — wrong advice for npm users.

-
-
-

After

-

findWebDir() throws with install-specific guidance: npm install -g for npm users, pnpm install && pnpm build for source users. Redundant checks in start.ts and dashboard.ts removed.

-
-
-
    -
  • packages/cli/src/lib/web-dir.ts — throw instead of returning broken path
  • -
  • packages/cli/src/commands/start.ts — removed redundant existsSync check
  • -
  • packages/cli/src/commands/dashboard.ts — removed redundant check + unused imports
  • -
- -

11. Fix CI-silent failure in setup scripts

-
-
-

Before

-

In non-interactive mode, npm link failure printed a message but the script continued with exit code 0. CI pipelines saw green while ao was not actually linked.

-
-
-

After

-

Non-interactive npm link failure now calls exit 1, making CI pipelines correctly report the failure.

-
-
-
    -
  • scripts/setup.sh
  • -
  • scripts/ao-update.sh
  • -
- -

12. Deduplicate detectDefaultBranch

-
-
-

Before

-

The same 30-line function (3-method fallback: symbolic-ref → GitHub API → local refs) was copy-pasted in both init.ts and add-project.ts. Bug fixes to one wouldn't propagate to the other.

-
-
-

After

-

Single implementation in packages/cli/src/lib/git-utils.ts, imported by both commands.

-
-
-
    -
  • packages/cli/src/lib/git-utils.ts — shared implementation
  • -
  • packages/cli/src/commands/init.ts — imports from git-utils
  • -
  • packages/cli/src/commands/add-project.ts — imports from git-utils
  • -
- -

Known Limitations

-

The following issues were investigated and determined to not need fixes:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IssueVerdictReason
node-pty optional — terminal panels broken?Not an issueMain terminal view uses ttyd (iframe), not node-pty. Direct terminal is a separate feature that degrades gracefully with a server-side log.
Git remote regex is GitHub-onlyBy designAO only ships GitHub SCM/tracker plugins. Non-GitHub repos would fail at ao spawn anyway. A warning could be added later.
start-all.ts cleanup doesn't wait for childrenNot an issueChildren are independent OS processes that handle SIGTERM on their own. next start is read-only (no cache writes). On Ctrl+C, the terminal sends SIGINT to the entire process group regardless.
ao add-project crashes on malformed YAMLAcceptableThe yaml library produces readable errors with line/column numbers. Same pattern as loadConfig() in core. Not worth a special wrapper.
ao add-project non-atomic writeNot an issuewriteFileSync is effectively atomic for small files (config is well under the OS block size). Same pattern used everywhere in the codebase.
EACCES on npm install -g is docs-onlyUnfixableThe error happens inside npm before any of our code runs. A postinstall script only executes after successful install. Documentation is the correct mitigation.
- -

New Onboarding Flow

- -

npm install path (most users)

-
-
-

Before (5+ min, multiple failure points)

-
-
1
-
git clone ...
Required even for non-contributors
-
-
-
2
-
bash scripts/setup.sh
Could crash on npm link
-
-
-
3
-
cd ~/my-project && ao init
13 interactive prompts
-
-
-
4
-
nano agent-orchestrator.yaml
Edit YAML to add projects
-
-
-
5
-
ao start
Could crash on port conflict
-
-
-
6
-
ao spawn my-project 123
User has to remember this themselves
-
-
-
-

After (under 1 min, fully guided)

-
-
1
-
npm install -g @composio/agent-orchestrator
One command, no clone needed
-
-
-
2
-
ao start https://github.com/org/repo
Clones, configures, and launches — prints "ao spawn"
-
-
-
3
-
ao spawn my-project 123
Start working!
-
-
-
- -

Source install path (contributors)

-
-
-

Before

-
-
1
-
bash scripts/setup.sh
Could crash on npm link. No next-step guidance.
-
-
-
2
-
cd ~/my-project
User has to figure out "where do I go?" on their own
-
-
-
3
-
ao init
13 interactive prompts
-
-
-
4
-
ao start
Could crash on port conflict
-
-
-
-

After (guided)

-
-
1
-
bash scripts/setup.sh
Handles permissions + prints "cd ~/your-project && ao init"
-
-
-
2
-
cd ~/my-project && ao init
Zero prompts + prints "ao start" with directory context
-
-
-
3
-
ao start
Auto-finds free port + prints "ao spawn <project> <issue>"
-
-
-
4
-
ao spawn my-project 123
Start working!
-
-
-
- -

UX Impact

- -
-
Before vs After — Quantitative Metrics
- -
-
-
Prompts in ao init
-
Before
-
13
-
-
-
-
After
-
0
-
-
- -
-
-
Steps to ao spawn
-
Before
-
6+
-
-
-
-
After (npm)
-
3
-
-
- -
-
-
Fatal crash points
-
Before
-
3
-
-
-
-
After
-
0
-
-
- -
-
-
Manual config edits
-
Before
-
Yes
-
-
-
-
After
-
No
-
-
- -
-
-
Dead CLI flags
-
Before
-
1
-
-
-
-
After
-
0
-
-
-
- -
-
-
13
-
-
0
-
Prompts in ao init
-
-
-
6+
-
-
3
-
Steps to first ao spawn (npm)
-
-
-
3
-
-
0
-
Fatal crash points
-
-
- - - - - - - - - - - - - - - - - - - -
MetricBeforeAfter
Steps to first ao spawn6+ (with manual edits)3 via npm, 4 from source
Interactive prompts in ao init130
Permission errors during setupFatal crashAuto-handled
Port conflicts on ao startFatal crashAuto-resolved
Adding a second projectManual YAML editingao add-project <path>
Dead CLI flags--smart shows "coming soon"Removed
"What's next" guidanceNone — user guessesEvery command prints the next step + directory
Dashboard for npm usersFatal crash (@composio/ao-web not published)Published + production entry point
Dashboard startup speed (npm)npx next start (slow global lookup)Direct node_modules/.bin/next binary
Error messages for missing packagesGeneric "Run: pnpm install" for all usersInstall-specific guidance (npm vs source)
CI reliabilityScripts exit 0 on npm link failureNon-interactive failures exit 1
Code duplicationdetectDefaultBranch copied in 2 filesSingle shared implementation
- -

Backward Compatibility

-
-
    -
  • ao init --auto still works (now equivalent to ao init)
  • -
  • ✓ Interactive wizard still available via ao init --interactive
  • -
  • ao start auto-port was already the behavior for URL-based starts — now default for all
  • -
  • ✓ No config format changes
  • -
  • ✓ No breaking changes to any existing command
  • -
-
- -

Related Issues

-

- #454 — Codex plugin stability improvements (spawned as a parallel agent session)
- #456 — Dashboard shows orchestrator as "Exited" due to startup race condition -

- -

Test Plan

-
    -
  • Fresh bash scripts/setup.sh on macOS without sudo permissions pre-granted
  • -
  • ao init in a git repo with GitHub remote → generates correct config with zero prompts
  • -
  • ao init --interactive → shows full 13-prompt wizard
  • -
  • ao add-project ~/some-repo → appends project with correct detection
  • -
  • ao add-project on already-added project → shows clear error
  • -
  • ao start with port 3000 busy → auto-finds 3001 and starts
  • -
  • ao update with permission issue → auto-retries with sudo
  • -
  • ao doctor --fix with permission issue → auto-retries with sudo
  • -
  • setup.sh completion → prints cd ~/your-project && ao init
  • -
  • ao init completion → prints ao start with directory context
  • -
  • ao start completion → prints ao spawn <project> <issue>
  • -
  • ao add-project completion → prints ao start and ao spawn
  • -
  • npm install -g @composio/agent-orchestratorao start finds and launches dashboard (no "Could not find @composio/ao-web" error)
  • -
  • npm-installed ao start launches via dist-server/start-all.js (production mode), not pnpm run dev
  • -
  • Dashboard package size is under 5 MB on npm (no .next/cache bloat)
  • -
  • Direct terminal gracefully degrades when node-pty is unavailable
  • -
  • Production dashboard uses local node_modules/.bin/next, not npx (check start-all.ts)
  • -
  • findWebDir() throws with install-specific message when package is missing (not a silent broken path)
  • -
  • setup.sh in non-interactive mode (e.g. bash setup.sh < /dev/null) exits 1 on npm link failure
  • -
  • ao-update.sh in non-interactive mode exits 1 on npm link failure
  • -
  • detectDefaultBranch is imported from lib/git-utils.ts in both init.ts and add-project.ts (no duplication)
  • -
-
- - diff --git a/docs/specs/runtime-terminal-port-and-project-id-hardening.html b/docs/specs/runtime-terminal-port-and-project-id-hardening.html deleted file mode 100644 index 01958b0b8..000000000 --- a/docs/specs/runtime-terminal-port-and-project-id-hardening.html +++ /dev/null @@ -1,573 +0,0 @@ - - - - - - Runtime Terminal Port and Project-ID Hardening - - - -
-

Runtime Terminal Port and Project-ID Hardening

- -
-
Status: Draft
-
Author: Agent Orchestrator
-
Date: 2026-03-28
-
Target Merge: main
-
- -
- -

Overview

-

- This design documents two production issues that surfaced in first-run and npm-installed flows: -

-
    -
  1. Direct terminal stuck on CONNECTING... when runtime ports differ from client bundle fallback.
  2. -
  3. /api/spawn receiving a session ID as projectId, leading to deep-core Unknown project failures.
  4. -
-

- The implementation introduces runtime configuration discovery for terminal WebSocket connection and stricter semantic validation for project identifiers at API and page-data boundaries. -

- -

Problem Statement

- -

Problem A: Terminal WebSocket Port Drift

-
    -
  • ao start can auto-select terminal ports at runtime (for example 14802/14803) when defaults are occupied.
  • -
  • Direct terminal server listens on DIRECT_TERMINAL_PORT at runtime.
  • -
  • Browser client previously relied on build-time NEXT_PUBLIC_DIRECT_TERMINAL_PORT, with fallback 14801.
  • -
  • In prebuilt Next.js client bundles, NEXT_PUBLIC_* values are embedded at build time.
  • -
  • Result: client can attempt ws://...:14801 while server is on 14803, leaving UI in permanent CONNECTING.
  • -
- -

Problem B: Project ID / Session ID Domain Confusion

-
    -
  • Session IDs and project IDs share syntax ([a-zA-Z0-9_-]+).
  • -
  • Orchestrator session IDs are generated as ${project.sessionPrefix}-orchestrator, which can visually resemble project keys.
  • -
  • /api/spawn previously validated only identifier shape, not membership in config.projects.
  • -
  • Invalid but syntactically valid IDs reached core spawn logic and failed late with 500.
  • -
- -

Root Cause Analysis

-

A. Build-Time vs Runtime Config Boundary Mismatch

-
    -
  • The server process controls actual runtime port assignment.
  • -
  • The browser bundle cannot safely depend on build-time env values for runtime-selected ports.
  • -
  • There was no first-party runtime endpoint for client port discovery.
  • -
- -

B. Namespace Collision and Inconsistent Validation

-
    -
  • Project IDs and session IDs were treated as plain strings at API boundaries.
  • -
  • Some routes sanitize project filters against config; others previously did not.
  • -
  • Validation was format-only in /api/spawn instead of semantic (is configured project).
  • -
- -

Goals

-
    -
  1. Make direct terminal connection deterministic across runtime-selected ports in prebuilt deployments.
  2. -
  3. Ensure /api/spawn rejects non-configured project IDs early and predictably.
  4. -
  5. Normalize dashboard project filter values so invalid query state cannot poison project context.
  6. -
  7. Preserve backwards compatibility for existing default-port setups.
  8. -
- -

Non-Goals

-
    -
  1. Redesign session ID format.
  2. -
  3. Introduce full typed ID wrappers across all packages in this change.
  4. -
  5. Remove existing reverse-proxy path-based WS support.
  6. -
- -

Proposed Design

- -

1. Runtime Terminal Config Endpoint

-

Add GET /api/runtime/terminal (dynamic, no-store):

-
    -
  • terminalPort from TERMINAL_PORT (normalized, fallback 14800)
  • -
  • directTerminalPort from DIRECT_TERMINAL_PORT (normalized, fallback 14801)
  • -
  • proxyWsPath from TERMINAL_WS_PATH/NEXT_PUBLIC_TERMINAL_WS_PATH (normalized path or null)
  • -
- -

2. Runtime-Aware DirectTerminal Connection

-

Update client connection flow:

-
    -
  1. Resolve build-time values if available.
  2. -
  3. Fetch /api/runtime/terminal before socket connect when needed.
  4. -
  5. Parse and normalize returned values.
  6. -
  7. Build WS URL from runtime values.
  8. -
  9. Reuse the same runtime-aware logic on reconnect attempts.
  10. -
-

Default ports remain unchanged; runtime-shifted ports become deterministic.

- -

3. Semantic Project Validation in /api/spawn

-

Before calling spawn, verify config.projects[projectId] exists. If missing:

-
    -
  • Return 404 with Unknown project: <id>
  • -
  • Record structured observability failure reason
  • -
  • Do not invoke core spawn path
  • -
- -

4. Dashboard Project Filter Normalization

-
    -
  • keep "all"
  • -
  • keep only configured project IDs
  • -
  • otherwise fallback to first valid configured project (or primary fallback)
  • -
- -

Flow Chart

- - - - -
-
Terminal Connection Flow
-
- -
- 1. User runs ao start <project-path> -
-
- -
- 2. CLI runtime setup — buildDashboardEnv() - Picks TERMINAL_PORT / DIRECT_TERMINAL_PORT at runtime -
-
- -
- 3. start-all launches servers - Next.js server + direct-terminal-ws on DIRECT_TERMINAL_PORT -
-
- -
- 4. Browser opens session page -
-
- -
- 5. resolveConnectionConfig() - Build-time NEXT_PUBLIC_* values available and valid? -
-
- -
-
- Yes -
-
- Use build-time values directly -
-
-
- No -
-
- GET /api/runtime/terminal - Read runtime env ports, normalize, return config -
-
-
- -
merge
- -
- 6. Client builds WS URL from resolved runtime config -
-
- -
- 7. WebSocket connect to direct-terminal-ws -
-
- -
- 8. Resolve tmux session - Exact match first → hash-prefixed suffix fallback -
-
- -
- 9. PTY attach succeeds — terminal interactive (CONNECTED) -
- -
-
- - -
-
Spawn Path
-
- -
- A. User triggers spawn - Dashboard / mobile / API caller -
-
- -
- B. POST /api/spawn with body.projectId -
-
- -
- C. validateIdentifier(projectId) - Syntax check — format only -
-
- -
- D. Semantic guard: config.projects[projectId] exists? -
-
- -
-
- Yes -
-
- sessionManager.spawn() -
-
-
- 201 Created + session payload - Dashboard/SSE shows active state -
-
-
- No -
-
- 404Unknown project: <id> - Stop early — core spawn not invoked -
-
-
- -
-
- - -
-
Project Filter Normalization
-
-
- Incoming ?project=<value> from dashboard query -
- - - - - - - - - - - - - - - - - - - - - -
ConditionResult
value == "all"Keep "all"
value ∈ configured projectsKeep value
otherwise (e.g. session ID like mono-orchestrator)Fallback to primary valid project
-

- Invalid values cannot become active project context. -

-
-
- -

Alternatives Considered

-
    -
  1. Keep fixed ports only (14800/14801) and disable auto-shift. Rejected: blocks multi-instance startup and fails on legitimate port conflicts.
  2. -
  3. Continue relying on NEXT_PUBLIC_* runtime injection. Rejected: production client bundles are build-time materialized.
  4. -
  5. Accept any projectId in /api/spawn and let core throw. Rejected: late 500 errors and poor API ergonomics.
  6. -
- -

Risks and Mitigations

-
    -
  1. Runtime endpoint unavailable. Mitigation: client retains safe fallback and reconnect logic.
  2. -
  3. Reverse-proxy deployments with custom WS path. Mitigation: preserve proxy-path precedence and include runtime proxy field.
  4. -
  5. Behavior change for invalid project query. Mitigation: normalization affects only unknown IDs; valid IDs and all remain unchanged.
  6. -
- -

Validation Plan

-
    -
  • GET /api/runtime/terminal returns runtime env ports.
  • -
  • POST /api/spawn returns 404 for unknown project and does not call spawn.
  • -
  • Project filter normalization keeps valid IDs, keeps all, and falls back on unknown IDs.
  • -
  • Existing DirectTerminal URL construction tests remain green.
  • -
- -

Rollout Plan

-
    -
  1. Merge patch to main.
  2. -
  3. Release new npm package version containing web/client and API updates.
  4. -
  5. Announce behavior note: -
      -
    • default-port users unaffected
    • -
    • runtime-shifted port users no longer hit CONNECTING deadlock
    • -
    -
  6. -
- -

Release Checklist (Commands)

-
# 1) Push branch and open PR
-git push origin fix-runtime-terminal-projectid-hardening
-
-# 2) Ensure patch changeset exists (this PR includes one)
-ls .changeset/five-lamps-heal.md
-
-# 3) CI validation (already required by repo workflows)
-pnpm --filter @composio/ao-web test -- \
-  src/__tests__/api-routes.test.ts \
-  src/lib/__tests__/dashboard-page-data.test.ts \
-  src/components/__tests__/DirectTerminal.test.ts
-
-# 4) After PR merge: version packages from changesets
-pnpm changeset version
-
-# 5) Commit version bumps and changelogs
-git add .
-git commit -m "chore(release): version packages for runtime terminal + spawn hardening"
-
-# 6) Publish
-pnpm release
-
-# 7) Post-release smoke check
-npm i -g @composio/ao@latest
-ao start <project-path>
-# verify terminal works when direct terminal port is non-default
- -

Acceptance Criteria

-
    -
  1. Direct terminal connects in npm prebuilt flow even when runtime direct port is not 14801.
  2. -
  3. /api/spawn never returns 500 for unknown-but-valid-format project IDs.
  4. -
  5. Invalid project query values do not become active project state.
  6. -
  7. Existing default-port flows remain functional without configuration changes.
  8. -
-
- - diff --git a/docs/state-machine-stage2-evidence-recovery-plan.md b/docs/state-machine-stage2-evidence-recovery-plan.md deleted file mode 100644 index 6c9182eee..000000000 --- a/docs/state-machine-stage2-evidence-recovery-plan.md +++ /dev/null @@ -1,347 +0,0 @@ -# State Machine Stage 2 Plan: Evidence, Detecting, And Recovery - -Status: Draft -Primary issue: #95 -Target branch: `sessions-redone` -Inputs: -- `~/.ao/ao-ahead/human-work/redesign.md` -- `~/.ao/ao-ahead/aa-2/state-machine-redesign-rollout-plan.md` -- `docs/state-machine-redesign.md` -- `CLAUDE.md` - -## Purpose - -Stage 2 is where lifecycle classification stops being a pile of loosely competing probes and becomes an explicit evidence assessment system. - -The goal is not to add more statuses. The goal is to make AO answer six questions cleanly and in order: - -1. what evidence exists right now -2. how fresh and trustworthy that evidence is -3. whether the evidence agrees -4. whether the system should enter `detecting` -5. whether retries are still allowed -6. whether AO should recover automatically, wait for a human, or declare a terminal outcome - -This document is planning only. It defines policy, sequencing, and acceptance criteria for Stage 2. It does not prescribe code-level implementation details yet. - -## Scope - -Stage 2 covers: - -- evidence assessment for runtime, process, activity, and PR-adjacent signals -- `detecting` state semantics and exit rules -- signal disagreement classification -- stale evidence policy by source type -- bounded retry policy for uncertain or failed probes -- recovery rules for worker and orchestrator sessions -- decision logging needed to explain why AO chose a state - -Stage 2 does not cover: - -- agent-authored lifecycle reporting commands -- prompt changes to force explicit acknowledgment or waiting signals -- UI redesign beyond the minimum data contracts Stage 2 requires -- full PR workflow automation redesign - -## Design Position - -Stage 2 should treat runtime truth, process truth, activity truth, and PR truth as separate inputs to an assessment pass. No single weak signal should be allowed to kill a session. `detecting` is the controlled buffer state used when the system cannot yet defend a confident answer. - -The practical policy is: - -- prefer explicit facts over heuristics -- prefer `detecting` over a false terminal label -- prefer bounded waiting over infinite ambiguity -- prefer recovery when the session is plausibly resumable -- prefer human escalation when the evidence is conflicting after retries - -## Evidence Inventory - -Each assessment pass should normalize evidence into a common shape with `source`, `observedAt`, `freshness`, `reliability`, and `value`. - -### Source classes - -- runtime evidence - - tmux/session handle exists - - runtime plugin reports reachable or unreachable -- process evidence - - agent process scan reports running or not running - - pid metadata exists or is missing -- activity evidence - - structured agent activity - - AO-generated activity markers - - terminal output heartbeat only as weak fallback -- recovery evidence - - provider-specific session resumability - - known recovery action available -- PR evidence - - PR open, merged, closed, CI pending, CI failing, review comments -- operator evidence - - explicit user kill - - explicit user continue/resume request - -### Reliability tiers - -- `authoritative` - - explicit user kill - - successful runtime/plugin confirmation - - successful provider resumability check -- `strong` - - process running or not running from a validated probe - - fresh structured activity from AO or provider logs -- `supporting` - - recent terminal output without structured semantics - - existing metadata from prior successful scans -- `historical` - - stale activity - - previous assessment results - -## Assessment Model - -Every lifecycle pass should produce an evidence report before it produces a session state. - -### Pass order - -1. collect the latest evidence for runtime, process, activity, recovery, and PR -2. grade each evidence item for freshness and reliability -3. detect contradictions -4. decide whether the contradiction is resolvable within retry bounds -5. emit one of: - - confident classification - - `detecting` - - terminal outcome with explicit reason - - recovery-needed classification - -### Required outputs - -Each pass should produce: - -- session assessment outcome -- primary reason -- evidence summary -- disagreement summary if any -- retry counter state -- recovery recommendation if any - -## `detecting` State Semantics - -`detecting` is not a synonym for `unknown`. It is an active assessment window where AO is intentionally trying to resolve conflicting, missing, or failed evidence. - -### Enter `detecting` when - -- runtime and process signals disagree -- runtime is unreachable but fresh activity still exists -- process probe fails in a way that could be transient -- activity is recent enough to block a dead classification but not enough to confirm health -- recovery appears possible but the recovery preconditions are not yet confirmed -- required probes time out or return incomplete data - -### Do not enter `detecting` when - -- user kill is explicit and confirmed -- runtime and process are both confirmed dead and no recovery path exists -- session is intentionally terminal for a reason independent of liveness -- the same disagreement has already exhausted the retry budget - -### Exit `detecting` when - -- evidence converges on a confident non-terminal state -- evidence converges on a terminal outcome -- bounded retries are exhausted and the result must be escalated -- recovery succeeds and a new healthy state is established -- recovery is impossible and the terminal reason is clear - -## Signal Disagreement Handling - -Disagreement handling must be explicit. Stage 2 should classify disagreements instead of burying them inside ad hoc fallthrough logic. - -### Core disagreement classes - -- `runtime_alive_process_dead` -- `runtime_dead_process_alive` -- `runtime_dead_recent_activity` -- `process_alive_no_recent_activity` -- `probe_failed_runtime_unknown` -- `probe_failed_process_unknown` -- `pr_terminal_runtime_alive` -- `recovery_possible_runtime_uncertain` - -### Resolution policy - -- `runtime_alive_process_dead` - - treat as likely recoverable worker failure - - enter `detecting` - - retry process probe - - evaluate resumability before declaring terminal -- `runtime_dead_process_alive` - - treat process evidence as suspicious - - prefer `detecting` - - retry runtime probe - - downgrade process evidence if the runtime container/session cannot be confirmed -- `runtime_dead_recent_activity` - - treat stale-vs-live timing carefully - - recent structured activity blocks immediate death - - historical activity does not -- `process_alive_no_recent_activity` - - do not classify as dead - - remain in current workflow state or `idle`/`stuck` depending on broader context -- `pr_terminal_runtime_alive` - - do not kill solely because the PR state is terminal - - classify workflow and runtime separately - -## Stale Evidence Policy - -Stale evidence should explain history, not overrule current hard facts. - -### Freshness windows - -- structured activity - - `0s-60s`: strong support for liveness - - `61s-5m`: weak support for liveness, enough to block immediate death on its own - - `>5m`: historical only -- terminal heartbeat output - - `0s-30s`: supporting only - - `31s-2m`: weak historical context - - `>2m`: ignore for liveness decisions -- runtime/process probe results - - `0s-30s`: current - - `31s-90s`: usable but should be refreshed before a terminal classification - - `>90s`: stale for decisive outcomes -- PR state - - remains authoritative for PR truth - - does not become authoritative for runtime death - -### Rules - -- stale activity cannot by itself prove the session is alive -- stale activity can explain why AO avoided a dead classification earlier -- a terminal outcome requires current hard evidence or an explicit operator action -- recovery eligibility may use stale evidence only to rank options, not to assert that recovery is safe - -## Bounded Retry Policy - -`detecting` must be temporary. Every disagreement path needs a fixed retry budget and an explicit escalation outcome. - -### Retry budgets - -- transient probe failures - - retry up to 3 times - - exponential spacing such as immediate, 10s, 30s -- hard disagreement with partial evidence - - retry up to 2 full assessment passes after the initial pass -- recovery attempt verification - - allow 1 recovery attempt per assessment cycle unless the operator explicitly asks again - -### Retry stop conditions - -- decisive evidence arrives -- the retry budget is exhausted -- an operator action overrides automated handling -- the recovery subsystem returns a definitive non-recoverable result - -### Escalation after retries - -When retries are exhausted, AO should not loop forever. It should produce one of: - -- `stuck` with a clear reason if human action is needed -- terminal with explicit reason if death is defensible -- recovery-pending-human if recovery exists but is unsafe to auto-run - -## Recovery Rules - -Recovery must be policy-driven, not implicit. - -### Worker sessions - -Auto-recovery is allowed when: - -- the runtime is reachable or can be recreated safely -- the provider session is resumable or a new worker can be created with preserved context -- the failure reason is operational rather than semantic -- there is no explicit user kill or explicit human stop condition - -Human-gated recovery is required when: - -- the evidence is still contradictory after retries -- PR state suggests the worker should be preserved rather than replaced -- the replacement action could duplicate work or create PR confusion -- permissions or credentials are required - -Do not auto-recover when: - -- the user explicitly killed the session -- the provider reports the session as non-resumable and replacement would violate workflow policy -- the session is already intentionally terminal - -### Orchestrator sessions - -Orchestrators should have a higher bar for terminal classification and auto-recovery attempts because they coordinate other sessions. - -Policy: - -- prefer recovery over terminal classification -- require stronger evidence before declaring them dead -- never tie orchestrator death to PR state -- surface unresolved orchestrator disagreements prominently for human review - -## Recovery Outcome Matrix - -### Recover automatically - -- worker runtime reachable, process dead, resumable session available -- worker runtime reachable, process dead, replacement worker policy explicitly allows restart - -### Wait for human - -- retries exhausted with unresolved disagreement -- PR merged or closed but runtime is still alive and policy choice is needed -- recovery would create a second worker or reopen a completed workflow - -### Declare terminal - -- explicit user kill confirmed -- runtime and process both confirmed dead after fresh probes -- recovery path explicitly unavailable and workflow policy allows termination - -## Decision Logging Requirements - -Stage 2 needs durable reasoning trails so the dashboard and operators can understand why a status changed. - -Each assessment result should record: - -- prior session state and reason -- new session state and reason -- major evidence items considered -- freshness classification for each decisive signal -- disagreement code if one existed -- retry count and remaining budget -- recovery action attempted, skipped, or blocked - -## Acceptance Criteria - -Stage 2 is ready when the plan can support these guarantees: - -- AO no longer declares a session dead from one weak signal -- `detecting` has defined entry and exit conditions -- every disagreement class has a retry policy and an escalation outcome -- stale evidence has explicit freshness windows by source type -- worker and orchestrator recovery policies differ where operationally necessary -- every terminal outcome has a defendable reason -- operators can inspect why AO chose the current state - -## Suggested Implementation Sequence - -1. define evidence data shapes and disagreement codes -2. define freshness grading and stale evidence rules -3. extract lifecycle assessment into a dedicated evaluation step -4. wire `detecting` entry, exit, and retry counters -5. define recovery policy gates for worker versus orchestrator sessions -6. add decision logging and test scenarios for each disagreement class - -## Open Decisions To Confirm Before Coding - -- whether the activity freshness windows should differ by agent provider -- whether terminal output should be considered at all once structured activity exists -- whether worker replacement and session resume are separate recovery classes in Stage 2 or deferred to Stage 3 -- whether retry counters live in session metadata or are recomputed from assessment history -- whether `stuck` is emitted directly by Stage 2 or remains a later projection over assessment outcomes