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.
{
"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
| Field | Type | Default | Description |
|---|---|---|---|
port | number | 3000 | Web UI port |
db | string | ./data/symphony.db | SQLite database path (relative to project root) |
poll_interval_ms | number | 5000 | Orchestrator tick interval in milliseconds |
stall_timeout_ms | number | 1200000 | Kill agents running longer than this (0 = disabled) |
max_concurrent_agents | number | 8 | Global agent slot limit (0 = use CPU count) |
Agent Settings (agent)
| Field | Type | Default | Description |
|---|---|---|---|
provider | string | "claude-cli" | Agent CLI backend. Valid values: claude-cli, codex-cli. |
runtime | string | (derived) | Short alias derived from provider ("claude" or "codex"). Set automatically; users should set provider instead. |
model | string | "opus" | Default Claude model for agents |
turn_timeout_ms | number | 1200000 | Per-turn timeout for Claude CLI |
max_retry_backoff_ms | number | 300000 | Maximum backoff between retries |
max_retries | number | 15 | Max total runs per issue before circuit breaker |
Workspace Settings (workspace)
| Field | Type | Default | Description |
|---|---|---|---|
dir_name | string | ".symphony-workspaces" | Directory name for git worktrees |
base_branch | string | "main" | Base branch for worktree creation |
Project Name Override (project)
| Field | Type | Default | Description |
|---|---|---|---|
project.name | string | auto-detected | Override 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:
{
"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:
| Variable | Required? | Purpose | Degraded Behavior |
|---|---|---|---|
ANTHROPIC_API_KEY | No | Enables LLM-based learning classification in LearningConsolidator | Falls back to algorithmic substring-matching classifier |
SYMPHONY_AGENT_PROVIDER_OVERRIDE | No | Temporary override for agent CLI provider (debugging/testing) | Uses project or global config provider |
SYMPHONY_AGENT_RUNTIME_OVERRIDE | No | Temporary 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, thenexit(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 providerIf 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
| Field | Type | Default | Description |
|---|---|---|---|
plannerEnabled | boolean | false | Enable planner agent for large/complex issues |
workerEnabled | boolean | true | Enable worker agents |
judgeEnabled | boolean | true | Enable judge agents for PR review |
scannerEnabled | boolean | false | Enable background Scanner agent (explores codebase, creates backlog tickets) |
researcherEnabled | boolean | — | Deprecated alias for scannerEnabled. Has no effect — use scannerEnabled instead. |
researchIntervalMs | number | 600000 | Minimum interval between scanner runs (10 min) |
Agent Settings
| Field | Type | Default | Description |
|---|---|---|---|
agent.model | string | global default | Override model for this project |
agent.turn_timeout_ms | number | global default | Override turn timeout |
Concurrency
| Field | Type | Default | Description |
|---|---|---|---|
maxConcurrency | number | global limit | Max concurrent agents for this project |
phaseSlotQuota | number | maxConcurrency - 1 | Max slots for phase agents (research, architecture, grooming) |
workerSlotQuota | number | 1 | Slots reserved for workers when phase agents are running |
judgeSlotQuota | number | 25% of effective max | Max slots reserved for judge agents (rounded up, min 1) |
How Slot Admission Works
Every dispatch attempt passes four gates in sequence:
- Global gate —
running.size >= max_concurrent_agentsblocks all dispatch immediately - Per-project gate —
maxConcurrency(capped at the global max) limits per-project dispatch - Worker reservation — phase agents (research, architecture, grooming) stop dispatching when remaining slots fall to or below
workerSlotQuota - 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
| Field | Type | Default | Description |
|---|---|---|---|
baseBranch | string | "HEAD" | Base branch for this project's worktrees |
Workspace
| Field | Type | Default | Description |
|---|---|---|---|
workspace.dir_name | string | global default | Override worktree directory name |
Quality Gates
Quality gates are commands that agents run before creating PRs. Configure via the gates array:
{
"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:
| Field | Type | Description |
|---|---|---|
command | string | Shell command to run in the worktree |
label | string | Human-readable name shown in the UI |
required | boolean | Whether 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:
{
"riskThresholds": {
"lowLines": 50,
"highLines": 500,
"lowFiles": 3,
"highFiles": 15,
"sensitivePaths": ["server/database/*", ".env*", "symphony.config.json"]
}
}| Field | Type | Default | Description |
|---|---|---|---|
lowLines | number | 50 | Below this = low risk |
highLines | number | 500 | Above this = high risk |
lowFiles | number | 3 | Below this = low risk |
highFiles | number | 15 | Above this = high risk |
sensitivePaths | string[] | [] | Glob patterns that increase risk score |
Risk levels: low, medium, high.
Setup Wizard State
| Field | Type | Description |
|---|---|---|
wizardCompleted | boolean | Whether the setup wizard has run successfully |
wizardFailed | boolean | Whether the setup wizard failed (prevents retries) |
Workflow Override
| Field | Type | Description |
|---|---|---|
workflowOverridePath | string | Path to a custom workflow definition |
Phase Labels
Issues use a phase:* label namespace to track readiness:
| Label | Meaning |
|---|---|
phase:research | Needs investigation — dispatches researcher |
phase:architecture | Needs design — dispatches researcher |
phase:grooming | Needs task breakdown — dispatches planner |
phase:ready | Implementation-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
| Field | Type | Description |
|---|---|---|
phase | string | Must match filename (e.g., architecture.md has phase: architecture) |
contract_version | number | Version for claim validation (currently 1) |
dispatch_profile | string | researcher, planner, worker, or judge |
artifact_type | string | Identifier for the artifact kind (research_brief, design_doc, task_breakdown, implementation) |
artifact_path | string|null | Template with {identifier} placeholder, must be inside docs/tickets/. Null for phase:ready. |
required_sections | string[] | Section keys validated by normalized slug matching |
done_when | string[] | Conditions for completion |
validation.mode | string | structural, judge, or trust |
validation.structural_checks | string[] | Checks to run |
validation.escalate_if | string[] | Triggers that force judge review even on structural contracts |
next_phase | string|null | Phase 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
| Transition | Default Mode |
|---|---|
| research -> architecture | structural |
| architecture -> grooming | judge |
| grooming -> ready | structural |
Context Budget
Hard bounds on injected prompt context to prevent unbounded prompts. Configured in server/orchestrator/contextBudget.ts.
| Setting | Default | Description |
|---|---|---|
maxTotalTokens | 8000 | Total token budget for description + findings + learnings + comments + previous output |
maxArtifactTokens | 4000 | Budget for previous phase artifacts injected into phase agent prompts |
preferFullText | true | Try 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):
- Per-project config —
projects.configcolumn in the database (set via Settings UI orconfigure_projectMCP tool) - Local config —
symphony.config.local.json(gitignored, developer-local) - Global config —
symphony.config.json(committed, shared) - Built-in defaults — hardcoded in
server/utils/config.ts
For agent model selection specifically:
- Agent profile model (
agentProfiles.model) - Per-project model (
config.agent.model) - Global model (
symphony.config.json->agent.model) - Default:
"opus"