Skip to content

Start Command

Overview

The symphony start command is the primary entry point for launching Symphony. It orchestrates initialization of both the web UI server and the orchestrator daemon, auto-detects projects in the workspace, and manages graceful lifecycle for both processes. Users run this once to bring the entire system online.

Usage

symphony start [--port <number>] [--codex | --claude]

Flags

FlagTypeDefaultDescription
--port <number>integer 1–655353000Web server port. Applies to both dev (nuxt dev --port) and production (Nitro) modes. Does not affect the orchestrator process.
--codexbooleanUse Codex CLI as the agent provider. Persisted to symphony.config.json; future symphony start runs without a provider flag retain this setting. Mutually exclusive with --claude.
--claudeboolean✓ (implicit default)Use Claude CLI as the agent provider. Persisted to symphony.config.json; future symphony start runs without a provider flag retain this setting. Mutually exclusive with --codex.

Note: --codex and --claude are mutually exclusive. Passing both flags is an error. Omitting both on a subsequent run does not reset a previously persisted provider — it leaves symphony.config.json unchanged.

Examples

bash
symphony start                    # Default: port 3000, Claude CLI
symphony start --port 4000        # Custom web server port
symphony start --codex            # Switch to Codex CLI (persisted)
symphony start --claude           # Switch back to Claude CLI (persisted)

How It Works

Startup Sequence

1. PID Check The command reads .symphony/symphony.pid to check if Symphony is already running. If a PID exists and the process is alive (verified via process.kill(pid, 0)), startup fails with an error message directing the user to symphony status.

2. Database & Config Initialization

typescript
resetConfig()  // Clear cached config
const config = loadConfig(symphonyDir)
const db = getDbForPath(config.db)
runMigrations(db)

Migrations are idempotent, ensuring the schema is up-to-date for this run.

3. Project Auto-Detection The command scans the parent directory for projects using scanForProjects():

  • Walks sibling directories
  • Identifies projects by checking for .git, package.json, etc.
  • Returns list with project name, type (nodejs, php, etc.), and path

4. Project SyncsyncProjects(db, detected) creates or updates project records in the database. This ensures the web UI has a current list of available projects without manual registration.

5. Dual-Process Spawning Two child processes are spawned via ProcessManager:

ProcessDev CommandProd CommandReady Signal
Webnpx nuxt dev --port 3000node .output/server/index.mjsLocal: http or Listening
Orchestratornpx tsx server/orchestrator/index.tsnpx tsx server/orchestrator/index.ts[Orchestrator] Running

Each process logs prefixed output: [web] ... and [orchestrator] ....

6. Readiness Detection The ProcessManager watches stdout for regex patterns (e.g., Local:\s+http for Nuxt). Once matched, the process is marked ready. If a process exits with non-zero code, all processes are shut down immediately.

7. Graceful Shutdown SIGINT (Ctrl+C) and SIGTERM signals trigger:

  • Send SIGTERM to all managed processes
  • Wait up to 30 seconds for graceful exit
  • Force SIGKILL any remaining processes
  • Remove PID file

Key Components

FileResponsibility
cli/commands/start.tsMain start logic: PID check, DB init, project sync, process spawning
cli/utils/processManager.tsChild process spawning, stdout/stderr piping, readiness detection, graceful shutdown
cli/utils/pid.tsPID file I/O and process liveness checking
cli/utils/output.tsTerminal formatting (colors, tables, duration formatting)
cli/index.tsCLI routing and command dispatch

Design Decisions

1. Why prevent double-start with PID files?

  • Avoids port conflicts and orphaned processes
  • Simple, OS-level mechanism (reliable across platforms)
  • Alternative: DB flag — but filesystem PID file is more robust for process liveness

2. Why auto-detect projects instead of manual registration?

  • Reduces setup friction for multi-project workspaces
  • Keeps database in sync with reality automatically
  • Fallback: manual symphony list shows what was detected

3. Why spawn orchestrator via npx tsx?

  • Allows running orchestrator during development (no build step required)
  • In production, could be replaced with a precompiled Node binary
  • Trades startup overhead for dev flexibility

4. Why readiness patterns instead of waiting N seconds?

  • Regex-based readiness is reliable (catches actual startup completion)
  • Avoids false positives from slow disks or CI environments
  • Each process defines its own signal (Nuxt vs orchestrator differ)

5. Why 30-second shutdown timeout?

  • Balances user patience (Ctrl+C should be fast) with graceful shutdown (agents commit work)
  • Long enough for agent processes to finish current work
  • SIGKILL fallback ensures cleanup on stuck processes

6. Why manage both web + orchestrator in one command?

  • Simplifies user experience (one command starts everything)
  • Ensures orchestrator is always running when web UI is up
  • Could be split into separate commands, but coordination becomes harder

Known Gaps

  • Port conflicts: No check for pre-existing processes on the same port; startup will fail but error message is from Nuxt, not Symphony
  • Orphaned PID files: If Symphony crashes, PID file remains; requires manual cleanup or symphony scan to recover
  • Slow project sync: Large workspaces may stall on scanForProjects() during startup
  • No multi-instance mode: Cannot run multiple Symphony instances in parallel (even on different ports)
  • Process exit codes: If web or orchestrator crash, all processes die; no retry logic