Architecture
Symphony is an agent orchestration service that auto-detects projects, provides a kanban board, and dispatches Claude CLI agents to work on issues in isolated git worktrees.
Three-Process Model
Symphony runs as three cooperating processes sharing a single SQLite database:
Web (Nuxt SPA)
- Framework: Nuxt 4 (Vue 3 + Nitro), SSR disabled
- Purpose: Kanban board UI, issue detail with diff viewer, project settings, orchestrator status
- API: Nitro file-based API routes at
server/api/ - Styling: Tailwind CSS with dark mode (class strategy)
Orchestrator (Daemon)
- Entry point:
npm run orchestrator(separate Node.js process) - Purpose: Polls the database every 5 seconds, dispatches agents, manages worktrees, handles retries
- PID lock: Only one orchestrator instance allowed (
.symphony/orchestrator.pid) - Tick loop:
processPhaseTransitions()→detectPipelineMismatches()/assignMissingPhaseLabels()→reconcile()→processRetries()→dispatchIntakeBatches()→dispatch()→workerRegistry.runDue()on each tick (background workers run maintenance tasks: learning consolidation, artifact pruning, prompt metrics, WAL checkpointing)
For a detailed explanation of the tick loop state machine, counter lifecycle, exit handler decision trees, and race condition defenses, see Orchestrator Lifecycle.
MCP Servers (Per-Agent)
- Lifecycle: Ephemeral — one MCP server per agent session, communicating over stdio
- Purpose: Gives agents tools to update issues, record findings/learnings, create PRs
- Protocol:
@modelcontextprotocol/sdkover stdio transport - Context: Each server is initialized with
issueIdandprojectIdfor scoped access
Readiness Phase System
Issues pass through readiness phases before reaching implementation. Phases track maturity; status tracks execution.
Phase Pipeline
- Exactly one
phase:*label per ticket at any time - Researcher-created tickets start as
backlog+phase:research - Manually created tickets get
phase:readyimmediately - Workers only dispatch to
phase:readytickets - Existing tickets without
phase:*labels are treated as implicitphase:ready(migration mode)
Dispatch Table
| Phase | Profile | Artifact Path |
|---|---|---|
phase:research | researcher | docs/tickets/{id}/research.md |
phase:architecture | architect | docs/tickets/{id}/design.md |
phase:grooming | planner | docs/tickets/{id}/grooming.md |
phase:ready | worker | code changes + PR |
Completion Claims
Agents do not mutate workflow state directly. They produce an artifact and submit a structured completion claim via the complete_phase MCP tool. The orchestrator validates the claim (structural checks, optional judge review) and performs the atomic phase transition.
Transition Processor
On each tick, before dispatch:
- Precondition check — claim's
completed_phasematches current label,contract_versionmatches contract - Structural validation — artifact exists at resolved path, required sections present
- Mode dispatch —
structural(auto-advance),judge(dispatch judge for verdict), ortrust(structural only, skip qualitative review) - Atomic transition — remove current
phase:*label, add nextphase:*label, record audit comment - Failure — leave phase unchanged, add
needs-revisionlabel, attach feedback
Implementation: server/orchestrator/phaseTransition.ts
Artifact System
Every ticket gets a deterministic directory for phase artifacts:
docs/tickets/
SYM-033/
research.md # phase:research output
design.md # phase:architecture output
grooming.md # phase:grooming outputPaths are resolved from the contract's artifact_path template. Agents receive the resolved path in runtime context and write to it. Artifacts use YAML frontmatter for metadata (ticket_id, phase, artifact_type, contract_version, artifact_revision, status).
Artifacts with lasting value can be promoted into the project docs tree (docs/research/, docs/architecture/, docs/guides/) via the promote_to field in completion claims. Implementation: server/orchestrator/artifactPromotion.ts.
Prompt Architecture
prompts/
profiles/ # Identity: what kind of agent
researcher.md # gather facts, inspect code, identify uncertainty
planner.md # decompose work, produce ordered tasks
worker.md # implement safely, test, keep scope tight
judge.md # verify correctness, coverage, requirement fit
phases/ # Contracts: what artifact to produce
research.md # produce research brief
architecture.md # produce design doc
grooming.md # produce task breakdown
ready.md # implement, run gates, create PR
overrides/ # Optional profile+phase deltas (empty initially)Prompt composition order: workspace boundary, agent identity (profile), phase contract, profile-phase override (if exists), project custom instructions, CLAUDE.md/repo conventions, ticket-specific runtime context.
Key principle: Profile = capability (what kind of agent). Phase = assignment mode (what artifact to produce). Layers 1-4 define the workflow contract. Layers 5-7 provide local execution context and may not alter artifact requirements or transition semantics.
Data Flow
Issue Lifecycle
- Issues start in
backlogortodo - Orchestrator promotes backlog items to
todowhen no work is available - Orchestrator claims
todoissues by setting them toin_progressand dispatching an agent - Agents work in isolated git worktrees with MCP tool access
- Agents create a PR (
create_pr) which moves the issue toreview - Judge agents review the PR diff and approve or reject
- Approved PRs wait in
reviewfor human merge; rejected PRs return totodo
Phase agents (research, architecture, grooming) can dispatch to tickets in backlog or todo status — maturation work happens before delivery.
Dispatch Priority
The orchestrator dispatches agents in this order:
- Setup Wizard — runs first, before all other dispatch; auto-configures any project that has not yet completed wizard setup (
wizardCompletedis false andwizardFailedis false) - Judge — review completed work or phase artifacts (unblock the pipeline)
- Phase agents — tickets with
phase:research,phase:architecture,phase:grooming - Worker / Planner —
phase:readytickets; the planner handlesneeds-planningissues in the same candidate pass (not a separate priority slot) - Scanner — idle background scan for improvements (only when no todo work exists and backlog < 10); controlled by
scannerEnabledin project config
At least 1 slot is reserved for phase:ready workers when maxConcurrency >= 2 to prevent phase work from starving implementation. This behavior is tested in tests/integration/orchestrator/workerSlotStarvation.test.ts.
Agent Isolation
Each agent runs in a git worktree:
- Worktrees are created in
.symphony-workspaces/<identifier>/ - Branch naming:
symphony/<identifier>(e.g.,symphony/SYM-001) - Workspace boundary is injected via the system prompt (not via CLAUDE.md files)
- Project configs (
.env, etc.) are copied into the worktree - On exit, the orchestrator auto-commits any uncommitted changes
Database
- Engine: SQLite with WAL mode and
busy_timeout=5000 - ORM: Drizzle ORM with raw SQL migrations (
CREATE TABLE IF NOT EXISTS) - Location:
data/symphony.db(configurable viasymphony.config.json)
Tables
| Table | Purpose |
|---|---|
projects | Detected projects with config, identifier prefix, path |
issues | Kanban issues with status, priority, findings, learnings |
issue_dependencies | Dependency links between issues |
issue_comments | Comments from users, agents, and system |
agent_profiles | Per-type agent configurations (prompt, model) |
agent_runs | Execution history (status, duration, output, worktree path) |
pull_requests | PR records with diff snapshots, gate results, risk scores |
intake_batches | Batch intake processing jobs |
research_coverage | Per-file/concept coverage tracking for scanner agents |
notifications | In-app notifications for agent events and PR status |
issue_events | Audit log of issue field changes |
project_labels | Per-project label definitions |
JSON Columns
Several tables store structured data as JSON text columns:
issues.findings— structured findings (test results, bugs, patterns)issues.learnings— reusable patterns for future agentsissues.labels— string array of labelsissues.artifacts— phase artifact records (path, type, status)projects.config— per-project configuration (gates, thresholds, agent toggles)pull_requests.file_stats— per-file addition/deletion countspull_requests.gate_results— quality gate pass/fail resultspull_requests.risk_score— computed risk level with breakdown
Directory Structure
symphony/
├── app/ # Vue 3 SPA
│ ├── components/ # UI components (auto-imported)
│ ├── composables/ # Composables (auto-imported)
│ ├── pages/ # File-based routes
│ └── utils/ # Client utilities (auto-imported)
├── server/
│ ├── api/ # Nitro API routes
│ ├── database/ # Schema + migrations
│ ├── mcp/ # MCP server + tools
│ ├── orchestrator/ # Orchestrator daemon
│ ├── services/ # Business logic
│ └── utils/ # Server utilities (auto-imported in server)
├── prompts/ # Agent prompts (profiles/, phases/, overrides/)
├── cli/ # CLI commands
├── tests/ # Vitest tests
├── symphony.config.json # Runtime config
└── data/ # SQLite DB (gitignored)