Skip to content

Configuration

Symphony uses two levels of configuration: global settings in symphony.config.json and per-project settings stored in the database.

Global Configuration (symphony.config.json)

Located at the project root. All fields have sensible defaults.

json
{
  "port": 3000,
  "db": "./data/symphony.db",
  "poll_interval_ms": 5000,
  "stall_timeout_ms": 1200000,
  "max_concurrent_agents": 8,
  "agent": {
    "provider": "claude-cli",
    "model": "opus",
    "turn_timeout_ms": 1200000,
    "max_retry_backoff_ms": 300000,
    "max_retries": 15
  },
  "workspace": {
    "dir_name": ".symphony-workspaces",
    "base_branch": "main"
  }
}

Top-Level Settings

FieldTypeDefaultDescription
portnumber3000Web UI port
dbstring./data/symphony.dbSQLite database path (relative to project root)
poll_interval_msnumber5000Orchestrator tick interval in milliseconds
stall_timeout_msnumber1200000Kill agents running longer than this (0 = disabled)
max_concurrent_agentsnumber8Global agent slot limit (0 = use CPU count)

Agent Settings (agent)

FieldTypeDefaultDescription
providerstring"claude-cli"Agent CLI backend. Valid values: claude-cli, codex-cli.
runtimestring(derived)Short alias derived from provider ("claude" or "codex"). Set automatically; users should set provider instead.
modelstring"opus"Default Claude model for agents
turn_timeout_msnumber1200000Per-turn timeout for Claude CLI
max_retry_backoff_msnumber300000Maximum backoff between retries
max_retriesnumber15Max total runs per issue before circuit breaker

Workspace Settings (workspace)

FieldTypeDefaultDescription
dir_namestring".symphony-workspaces"Directory name for git worktrees
base_branchstring"main"Base branch for worktree creation

Project Name Override (project)

FieldTypeDefaultDescription
project.namestringauto-detectedOverride the auto-detected project name

Local Overrides (symphony.config.local.json)

Create symphony.config.local.json at the project root to override any setting from symphony.config.json without modifying the committed file. This file is automatically added to .gitignore by Symphony on first orchestrator boot.

Only include the fields you want to override — all other fields inherit from symphony.config.json and built-in defaults.

Example — use an absolute workspace path on your machine:

json
{
  "workspace": {
    "dir_name": "/fast-ssd/symphony-workspaces"
  }
}

Nested objects (agent, workspace, context_budget, background_workers, project) are shallow-deep-merged: setting agent.max_retries in the local file does not wipe agent.model from the committed config. Top-level keys are replaced outright.

Environment Variables

Symphony respects the following environment variables for runtime configuration and feature toggles:

VariableRequired?PurposeDegraded Behavior
ANTHROPIC_API_KEYNoEnables LLM-based learning classification in LearningConsolidatorFalls back to algorithmic substring-matching classifier
SYMPHONY_AGENT_PROVIDER_OVERRIDENoTemporary override for agent CLI provider (debugging/testing)Uses project or global config provider
SYMPHONY_AGENT_RUNTIME_OVERRIDENoTemporary override for agent runtime (debugging/testing)Uses project or global config runtime

Environment Variable Validation

The orchestrator validates all known environment variables at startup and logs their status:

  • Present vars: Confirmed with feature description
  • Missing optional vars: Warning with degradation explanation
  • Missing required vars: Error message with instructions, then exit(1)

Example startup log:

[Orchestrator] Environment variables validated
[Orchestrator]   ✓ ANTHROPIC_API_KEY present — LLM learning classifier enabled
[Orchestrator]   ⚠ SYMPHONY_AGENT_PROVIDER_OVERRIDE not set — Uses project or global config provider

If you see warnings for optional features you don't need, no action is required. The orchestrator continues with graceful degradation.

The env var registry lives in server/utils/envValidation.ts. When adding a new environment variable to the codebase, add an entry to ENV_REGISTRY in that file to ensure it is validated at startup and documented in one place.

Per-Project Configuration

Stored in the projects.config JSON column. Configurable through the web UI or the configure_project MCP tool.

Agent Toggles

FieldTypeDefaultDescription
plannerEnabledbooleanfalseEnable planner agent for large/complex issues
workerEnabledbooleantrueEnable worker agents
judgeEnabledbooleantrueEnable judge agents for PR review
scannerEnabledbooleanfalseEnable background Scanner agent (explores codebase, creates backlog tickets)
researcherEnabledbooleanDeprecated alias for scannerEnabled. Has no effect — use scannerEnabled instead.
researchIntervalMsnumber600000Minimum interval between scanner runs (10 min)

Agent Settings

FieldTypeDefaultDescription
agent.modelstringglobal defaultOverride model for this project
agent.turn_timeout_msnumberglobal defaultOverride turn timeout

Concurrency

FieldTypeDefaultDescription
maxConcurrencynumberglobal limitMax concurrent agents for this project
phaseSlotQuotanumbermaxConcurrency - 1Max slots for phase agents (research, architecture, grooming)
workerSlotQuotanumber1Slots reserved for workers when phase agents are running
judgeSlotQuotanumber25% of effective maxMax slots reserved for judge agents (rounded up, min 1)

How Slot Admission Works

Every dispatch attempt passes four gates in sequence:

  1. Global gaterunning.size >= max_concurrent_agents blocks all dispatch immediately
  2. Per-project gatemaxConcurrency (capped at the global max) limits per-project dispatch
  3. Worker reservation — phase agents (research, architecture, grooming) stop dispatching when remaining slots fall to or below workerSlotQuota
  4. Judge cap — judge agents get priority (dispatched first) but have their own slot cap of judgeSlotQuota, which defaults to 25% of the effective max concurrency

The worker reservation is conditional: it only activates when there are actually dispatchable worker items for the project. If all work is in pre-ready phases, phase agents can use all slots.

Judges can exceed the global max by their slot quota to prevent pipeline starvation. Projects with many sequential phases (research → architecture → grooming) benefit from higher judge allocation to unblock the pipeline faster.

Slot availability is always computed live from the in-memory running map — there is no persistent slot counter. If a process dies before its exit handler fires, the slot is freed on the next reconcile() pass (runs every tick before dispatch).

Dispatch Priority Within a Tick

1. Setup wizards    (bypass concurrency — run alongside regular agents)
2. Auto-unblock     (batch blocked→todo before dispatch begins)
3. Per-project:
   a. Judges        (highest — review bottleneck)
   b. Phase agents  (second — respects worker reservation)
   c. Workers       (third — implementation throughput)
   d. Planners      (fourth — decomposition)
   e. Scanners      (lowest — background discovery)

Branch

FieldTypeDefaultDescription
baseBranchstring"HEAD"Base branch for this project's worktrees

Workspace

FieldTypeDefaultDescription
workspace.dir_namestringglobal defaultOverride worktree directory name

Quality Gates

Quality gates are commands that agents run before creating PRs. Configure via the gates array:

json
{
  "gates": [
    { "command": "npx vitest run", "label": "Tests", "required": true },
    { "command": "npx nuxt build", "label": "Build", "required": true },
    { "command": "npx eslint .", "label": "Lint", "required": false }
  ]
}

Each gate:

FieldTypeDescription
commandstringShell command to run in the worktree
labelstringHuman-readable name shown in the UI
requiredbooleanWhether the gate must pass for PR approval

Gate results are stored on the PR record and displayed in the diff viewer.

Risk Scoring

Risk scores are computed automatically when a PR is created, based on diff size and affected paths:

json
{
  "riskThresholds": {
    "lowLines": 50,
    "highLines": 500,
    "lowFiles": 3,
    "highFiles": 15,
    "sensitivePaths": ["server/database/*", ".env*", "symphony.config.json"]
  }
}
FieldTypeDefaultDescription
lowLinesnumber50Below this = low risk
highLinesnumber500Above this = high risk
lowFilesnumber3Below this = low risk
highFilesnumber15Above this = high risk
sensitivePathsstring[][]Glob patterns that increase risk score

Risk levels: low, medium, high.

Setup Wizard State

FieldTypeDescription
wizardCompletedbooleanWhether the setup wizard has run successfully
wizardFailedbooleanWhether the setup wizard failed (prevents retries)

Workflow Override

FieldTypeDescription
workflowOverridePathstringPath to a custom workflow definition

Phase Labels

Issues use a phase:* label namespace to track readiness:

LabelMeaning
phase:researchNeeds investigation — dispatches researcher
phase:architectureNeeds design — dispatches researcher
phase:groomingNeeds task breakdown — dispatches planner
phase:readyImplementation-ready — dispatches worker

Exactly one phase:* label per ticket. Validation logic in server/utils/phaseLabels.ts.

Phase Contracts

Phase contracts live in prompts/phases/ as markdown files with YAML frontmatter. The frontmatter is machine-readable workflow policy; the markdown body is behavioral guidance.

Contract Fields

FieldTypeDescription
phasestringMust match filename (e.g., architecture.md has phase: architecture)
contract_versionnumberVersion for claim validation (currently 1)
dispatch_profilestringresearcher, planner, worker, or judge
artifact_typestringIdentifier for the artifact kind (research_brief, design_doc, task_breakdown, implementation)
artifact_pathstring|nullTemplate with {identifier} placeholder, must be inside docs/tickets/. Null for phase:ready.
required_sectionsstring[]Section keys validated by normalized slug matching
done_whenstring[]Conditions for completion
validation.modestringstructural, judge, or trust
validation.structural_checksstring[]Checks to run
validation.escalate_ifstring[]Triggers that force judge review even on structural contracts
next_phasestring|nullPhase to advance to on success, or null (terminal)

Contracts are schema-validated on orchestrator boot. Invalid contracts cause a startup warning and the phase becomes non-dispatchable. Parser: server/orchestrator/contractParser.ts.

Validation Modes

TransitionDefault Mode
research -> architecturestructural
architecture -> groomingjudge
grooming -> readystructural

Context Budget

Hard bounds on injected prompt context to prevent unbounded prompts. Configured in server/orchestrator/contextBudget.ts.

SettingDefaultDescription
maxTotalTokens8000Total token budget for description + findings + learnings + comments + previous output
maxArtifactTokens4000Budget for previous phase artifacts injected into phase agent prompts
preferFullTexttrueTry full artifact text first, fall back to structural summary

Budget allocation: description 25%, findings 20%, learnings 15%, comments 15%, previous output 25%. Uses ~4 chars/token heuristic. Most-recent items are kept when truncating.

Configuration Resolution Order

Settings are resolved with this priority (highest to lowest):

  1. Per-project configprojects.config column in the database (set via Settings UI or configure_project MCP tool)
  2. Local configsymphony.config.local.json (gitignored, developer-local)
  3. Global configsymphony.config.json (committed, shared)
  4. Built-in defaults — hardcoded in server/utils/config.ts

For agent model selection specifically:

  1. Agent profile model (agentProfiles.model)
  2. Per-project model (config.agent.model)
  3. Global model (symphony.config.json -> agent.model)
  4. Default: "opus"