Agent Types
Symphony dispatches four agent types, each with a specialized role. Agents are Claude CLI subprocesses that communicate with Symphony through MCP tools over stdio.
Phase-Aware Dispatch
Agents are dispatched based on both their profile (capability) and the issue's current phase (assignment mode):
| Phase Label | Dispatched Profile | Behavioral Contract |
|---|---|---|
phase:research | researcher | prompts/phases/research.md |
phase:architecture | researcher | prompts/phases/architecture.md |
phase:grooming | planner | prompts/phases/grooming.md |
phase:ready | worker | prompts/phases/ready.md |
Profile = capability (what kind of agent). Phase = assignment mode (what artifact to produce). The phase contract defines the behavioral mode, not the agent type alone. A researcher dispatched for phase:architecture behaves differently than one dispatched for phase:research, because each receives a different contract.
Write Permissions by Phase
Pre-ready agents have restricted write access enforced via phase contract prompts:
| Phase | May Write | May NOT Write |
|---|---|---|
phase:research | docs/tickets/<ID>/research.md, findings/comments | Source code, app/, server/, tests/ |
phase:architecture | docs/tickets/<ID>/design.md, findings/comments | Source code, app/, server/, tests/ |
phase:grooming | docs/tickets/<ID>/grooming.md, findings/comments | Source code, app/, server/, tests/ |
phase:ready | Any file in the worktree, PRs | N/A (full access) |
Worker
The default agent type. Workers implement issues — writing code, running tests, and creating PRs.
Trigger: todo issues with phase:ready label (or no phase label in migration mode), resolved dependencies, no needs-planning label.
Lifecycle:
- Orchestrator creates a git worktree and sets issue to
in_progress - Worker reads the issue, plans an approach, implements changes
- Worker runs quality gate commands (tests, build, lint)
- Worker commits changes and calls
create_prwith diff and gate results - Issue moves to
review
On failure: Retried up to max_retries times with exponential backoff. After max retries, moved to backlog.
On exit without status change: Treated as a continuation — retried with context from previous attempts.
Prompt: prompts/profiles/worker.md + prompts/phases/ready.md
Key behaviors
Workers follow a six-phase session model: Orient (read issue + prior findings/learnings) → Plan (decide approach, list files) → Implement (write code with tests) → Validate (run full test suite + build/lint) → Commit (git add -A && git commit) → Report (create_pr, add_finding, add_learning).
Hard constraints:
- Never remove tests that cover unique behavior — fix the code, not the test. Consolidating redundant tests into parameterized equivalents is encouraged.
- Never remove existing features — only implement the assigned issue
- Must run the full test suite, not just related tests
- Must commit; changes in the worktree are lost without a commit
- No external research (no WebSearch/WebFetch) — the implementation plan is already approved
Error recovery:
- Test failure: check if pre-existing (
git stash && test && git stash pop); if caused by your change, fix the code - Stuck after 2+ attempts: commit partial work with
wip:prefix, add agapfinding with root-cause theory, set status totodo - Merge conflict:
git fetch origin main && git rebase origin/main; complex conflicts → commit, add finding, settodo - Flaky test: run again to confirm; if non-deterministic, add finding and proceed
Judge
Reviews PRs created by workers. Judges are autonomous — they approve, reject, or block.
Trigger: Issues in review status with an open PR, respecting a 5-minute cooldown between judge attempts.
Lifecycle:
- Orchestrator dispatches judge with the PR diff included in the prompt
- Judge reads the issue requirements and reviews the diff
- Judge runs quality gate commands to verify
- Judge calls one of:
approve_pr— PR is approved, issue stays inreviewfor human mergereject_pr— PR is rejected, issue returns totodofor reworkupdate_issue_statustoblocked— needs human input
On exit without calling a tool: Left in review for retry. The judge's text output alone is not recorded as a decision — only MCP tool calls count.
Cooldown: 5 minutes between judge attempts per issue. Only counts judge profile runs, not worker runs.
Phase review: When reviewing phase artifacts (not PRs), the judge receives approve_phase and reject_phase tools instead of approve_pr/reject_pr. The MCP config is context-aware.
Prompt: prompts/profiles/judge.md
Key behaviors
The judge approves only when quality gates are green and the code fulfils the documented requirements. Both conditions must hold — a passing gate alone is not sufficient if requirements are unmet, and met requirements do not override failing gates.
Context sections provided to the judge (in addition to the diff):
- Change Summary — file-level stats (files changed, insertions, deletions per file)
- Risk Assessment — risk level, line/file counts, sensitive paths touched
- Worker's Approach — the worker's implementation finding (what they did and why)
- Gate Results — test/lint/build pass-fail status
Each section degrades gracefully if unavailable. The diff, description, and previous output each have separate context-budget allocations; all three are truncated proportionally before any one is dropped entirely.
Must-call-tool rule: The judge's text output is never recorded as a decision. Only MCP tool calls count. If the judge exits without calling a verdict tool, the issue stays in review for retry after the 5-minute cooldown.
Planner
Decomposes complex issues into smaller, independent sub-tasks. Disabled by default.
Trigger: todo issues with needs-planning label or large size (when plannerEnabled is true in project config).
Lifecycle:
- Orchestrator dispatches planner for the complex issue
- Planner reads the issue and project context
- Planner creates 2-6 sub-tasks using
create_subtask - Parent issue is set to
blockeduntil all sub-tasks aredone - When all sub-tasks complete, parent auto-unblocks to
todo
Rules:
- Planners do NOT write code — they only create sub-tasks
- Each sub-task should be completable in a single worker session
- Sub-tasks should include specific file paths and acceptance criteria
Phase dispatch: Also handles phase:grooming tickets via the grooming contract, producing task breakdowns as artifacts.
Prompt: prompts/profiles/planner.md (+ prompts/phases/grooming.md for phased tickets)
Key behaviors
- Minimum one sub-task required: A planner that exits without calling
create_subtaskis considered a failure and is added to the retry queue with reasonplanner-no-subtasks. If the issue doesn't need decomposition, it should not have theneeds-planninglabel. - Parent blocking: The parent issue is automatically set to
blockedwhen the first sub-task is created. It auto-unblocks totodowhen all children reachdone. - Sub-task constraints: Each sub-task should be completable in a single worker session and should include specific file paths and acceptance criteria.
- Dispatch exclusion: When
plannerEnabledis true, issues withneeds-planningor sizelargeare removed from the worker dispatch queue entirely — workers cannot claim them until decomposition completes.
Scanner
Scans the codebase for improvements and creates backlog items. Disabled by default.
Trigger: Runs as a background agent when:
- No
todoitems are waiting - Backlog has fewer than 10 items
- Configured interval has elapsed (default: 10 minutes)
Lifecycle:
- Orchestrator creates a temporary worktree for the scanner
- Scanner scans the codebase for gaps, tech debt, missing tests, etc.
- Scanner checks existing issues to avoid duplicates
- Scanner creates backlog items using
create_issue - Worktree is cleaned up on exit (scanner doesn't produce code)
What the scanner looks for:
- Missing tests or low coverage
- TODOs, FIXMEs, HACKs in code
- Error handling gaps
- Performance issues
- Security concerns
- Documentation gaps
- Dead code or unused dependencies
Config toggle: scannerEnabled in project config (default: false).
Prompt: prompts/profiles/scanner.md
Key behaviors
- No auto-commit: Scanners do not commit to git. All output is recorded via MCP tools (
create_issue,add_finding,add_learning). - Duplicate prevention: Before creating a backlog item, the scanner calls
list_issuesto check for existing coverage. - Worktree cleanup: The temporary worktree is removed on exit; no code artifacts are left behind.
Researcher
A phase agent dispatched to tickets in phase:research or phase:architecture. Unlike the Scanner, the Researcher works on an assigned ticket and produces a required artifact before the ticket can advance.
Trigger: Dispatched by the orchestrator when a ticket carries a phase:research or phase:architecture label and the researcherEnabled toggle is on in project config.
Lifecycle:
- Orchestrator dispatches the researcher with the phase contract in the prompt
- Researcher reads the issue, explores relevant code, and gathers context
- Researcher produces the required artifact (
docs/tickets/<ID>/research.mdordesign.md) - Researcher calls
complete_phase— orchestrator validates the artifact and advances the phase label
Key distinction from the Scanner: The Researcher is ticket-bound and contract-bound. It works in a worktree scoped to a specific issue and must deliver a structured artifact with required sections. The Scanner has no assigned ticket, no required artifact, and only creates backlog items.
Config toggle: researcherEnabled in project config.
Prompt: prompts/profiles/researcher.md (+ prompts/phases/research.md or prompts/phases/architecture.md)
Key behaviors
For phase:research — the researcher produces docs/tickets/<ID>/research.md. Required sections are defined by the research phase contract. On calling complete_phase, the orchestrator validates the artifact and advances the label to phase:architecture (or skips ahead for simple tickets via skip_to).
For phase:architecture — the researcher acts as the Architect: synthesizes research into a concrete design doc (docs/tickets/<ID>/design.md) with 6 required sections (Problem, Proposed Approach, Alternatives Considered, Impacted Components, Risks, Rollout). Architecture completion is judge-gated before advancing to phase:grooming.
Design principles applied in architecture mode: simplicity first, incremental delivery, separation of concerns, backward compatibility (use safeAlterTable for schema changes), testability.
Tool restrictions (architecture mode): read-only access to source code; may only write to docs/tickets/<ID>/. Cannot call status, PR, or subtask tools.
External research: If externalResearch is enabled in project config, the researcher may use WebSearch/WebFetch (default max 5 searches per session) to verify library health, check for breaking changes, or find canonical pattern implementations. Safety rule: never search for credentials, keys, or internal URLs.
Idle-only dispatch (scanner mode vs. researcher mode applies only to the Scanner profile; the Researcher profile is dispatched whenever a phase:research or phase:architecture ticket exists, regardless of backlog size).
Setup Wizard
A special agent that runs once per project on first detection. Not a regular dispatch type.
Trigger: New project detected without wizardCompleted or wizardFailed in config.
Lifecycle:
- Orchestrator dispatches wizard to analyze the project
- Wizard reads project structure, configs, and conventions
- Wizard calls
configure_projectto set quality gates, sensitive paths, and risk thresholds - On success,
wizardCompletedis set in project config - On failure,
wizardFailedis set to prevent retry loops
Agent Profiles
Agent behavior is customizable through the agent_profiles table:
| Field | Purpose |
|---|---|
type | planner, worker, judge, researcher, or custom |
systemPrompt | Custom instructions appended to the default prompt |
model | Override model (null = use project/global default) |
projectId | Scope to a specific project (null = global default) |
isDefault | Whether this is the default profile for its type |
Default profiles are seeded on project detection. Per-project profiles override global defaults.
Concurrency and Slot Management
The orchestrator manages agent slots:
- Global limit:
max_concurrent_agentsinsymphony.config.json(0 = use CPU count) - Per-project limit:
maxConcurrencyin project config - Reserved slots: Judge agents get priority to prevent review starvation
- Dispatch order: Setup Wizard (tier 0, before all else) → Judges → Phase agents → Workers/Planners/Researchers
Retry and Circuit Breaker
- Max retries: Configurable per-project (default: 15)
- Backoff: Exponential with configurable max (
max_retry_backoff_ms) - Circuit breaker: After max retries, issue moves to
backlogwith a system comment - Total run tracking: Counts all runs (not just consecutive failures) to prevent runaway loops