Prompt Engineering Architecture
How Symphony assembles prompts, what context flows to each agent type, and how to write effective custom instructions.
Key source files:
server/orchestrator/promptBuilder.ts— builds system and user prompts (legacy + phase)server/orchestrator/worktreeSetup.ts— generates workspace boundary textserver/orchestrator/agentRunner.ts— launches Claude CLI with assembled promptsserver/orchestrator/contextBudget.ts— token budget system for user prompt sectionsserver/learning/processor.ts— gathers, scores, deduplicates, and splits learnings into conventions vs tacticalserver/orchestrator/contractParser.ts— parses phase contract frontmatterserver/orchestrator/frontmatterParser.ts— shared YAML frontmatter parser (used by contracts and profiles)server/orchestrator/profileParser.ts— parses agent profile definitions from markdown with YAML frontmatterprompts/profiles/*.md— agent type prompt templates with YAML frontmatter metadataprompts/phases/*.md— phase contract prompts
Table of Contents
- Prompt Assembly Pipeline
- Context Flow Per Agent Type
- Prompt Template Files
- Token Budget System
- Custom Instructions Guide
- MCP Tools Available to Agents
- Troubleshooting
1. Prompt Assembly Pipeline
Symphony has two dispatch paths that assemble prompts differently: legacy profile dispatch (for standard issue work) and phase contract dispatch (for pre-ready pipeline phases). Both produce a system prompt and a user prompt passed to the Claude CLI.
Path A: Legacy Profile Dispatch
Used by dispatchAgent() for standard work (implement, review, plan) and dispatchResearch() for background scanning.
System Prompt Layers
The workspace boundary is prepended separately (in agentDispatcher.ts) before the system prompt built by buildSystemPrompt(). Additionally, the worktree path and branch are reinforced in the user prompt header for worker and judge dispatches.
Assembly flow:
getWorkspaceBoundary(worktreePath, identifier, branchName)returns workspace boundary text (includesFIRST ACTION: pwdverification)buildSystemPrompt({ projectRoot, promptsDir, agentType, vaultContextLayer })returns profile + web restriction + external research + conventions + vault context- Concatenation:
workspaceBoundary + '\n\n---\n\n' + baseSystemPrompt - Written to temp file via
writeSystemPromptFile() - Passed to
claude -p --system-prompt <path>
User prompt reinforcement (worker and judge only):
buildUserPrompt() accepts an optional worktreeContext field. When provided, it renders a blockquote header between the issue title and priority line:
> **Working in**: `/path/to/worktree` | **Branch**: `symphony/SYM-XXX`Profile and phase contract reinforcement:
All agent profiles include a "Step 0: Verify Workspace" section instructing agents to run pwd and git branch --show-current as their first action. All phase contracts include a "Workspace Context" section with the same verification commands.
Convention Learnings System
Convention learnings are a distinct subsystem that injects project-wide rules into the system prompt for legacy dispatch agents. This is separate from the "Related Learnings" section in the user prompt, which contains only tactical learnings.
How it works (code in server/learning/processor.ts):
gatherAndSplitLearnings()collects all learnings from sibling issues in the same project- Learnings are deduplicated (substring containment — longer pattern wins) and relevance-scored
- Learnings are split by
learning_type:conventiontype → injected into system prompt as Layer 4 (capped at 1,500 characters viaCONVENTION_CHAR_CAP)- All other types (
pattern,gotcha, unset) → passed to user prompt as "Related Learnings" (up to 25 items viaDEFAULT_MAX_RESULTS)
Relevance scoring weights (applied to both convention and tactical learnings):
| Factor | Weight | Algorithm |
|---|---|---|
| Label overlap | 35% | Jaccard similarity between issue labels and source issue labels |
| Path match | 30% | Substring containment of applies_to paths in issue text |
| Keyword overlap | 20% | Jaccard similarity of tokenized text (stop words filtered) |
| Recency | 15% | Linear decay over 90 days (1.0 today, 0.0 at 90 days) |
Key details:
- Convention learnings are sorted by relevance score, then taken until the 1,500-char cap is reached
- Deduplication uses normalized patterns (lowercase, collapsed whitespace, stripped punctuation) and substring containment
- The
learning_typefield is optional — learnings without it default to tactical (not convention) - Phase agents do not receive convention learnings (only legacy dispatch agents do)
User Prompt (9 sections, token-budgeted)
Built by buildUserPrompt() after applyContextBudget() trims sections to fit within the 8,000-token default budget.
Path B: Phase Contract Dispatch
Used by dispatchPhaseAgent() for pre-ready pipeline phases (research, architecture, grooming).
System Prompt Layers
Built by buildPhaseSystemPrompt() in promptBuilder.ts. Key difference from legacy: custom instructions go in the system prompt for phase agents (Layer 4), not the user prompt.
User Prompt (phase-specific)
Phase agents get a simplified user prompt built inline in dispatchPhaseAgent():
Path C: Researcher Dispatch
Used by dispatchResearch(). The researcher bypasses buildUserPrompt() entirely:
- System prompt: same legacy layer structure (profile + external research guidance + convention learnings)
- User prompt: fixed instruction string + tactical learnings + optional custom instructions + workspace project list (for multi-project awareness)
CLI Invocation
The final command launched by agentRunner.ts:
claude -p \
--system-prompt <temp-file-path> \
--model <model> \
--mcp-config <mcp-config-path> \
--strict-mcp-config \
--dangerously-skip-permissions \
--disable-slash-commands \
--output-format stream-json \
--verbose \
"<user-prompt-string>"Environment variable: CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000
The system prompt is written to a temp file (not passed inline) because Claude CLI expects --system-prompt <path>. Temp files are cleaned up after agent exit.
Key design decisions:
- Workspace boundary is injected into the system prompt, NOT written as a CLAUDE.md file in the worktree (prevents merge conflicts and diff pollution)
- For Claude CLI (
claude-cliprovider): CLAUDE.md is read implicitly by the CLI — no injection needed - For Codex CLI (
codex-cliprovider): CLAUDE.md must be injected into the system prompt explicitly (injectmode) - For legacy dispatch, custom instructions go in the user prompt (lower authority)
- For phase dispatch, custom instructions go in the system prompt (higher authority)
- Worker agents can override their profile prompt entirely via a
WORKFLOW.mdfile in the project root
2. Context Flow Per Agent Type
System Prompt Content
| Layer | Worker | Judge | Planner | Researcher | Setup Wizard | Phase Agents |
|---|---|---|---|---|---|---|
| Workspace boundary | Yes | Yes | Yes | Yes | Yes | Yes |
| Profile prompt | profiles/worker.md | profiles/judge.md | profiles/planner.md | profiles/researcher.md | setup-wizard-system-prompt.md | — |
| Phase contract | — | — | — | — | — | phases/<phase>.md |
| Profile-phase override | — | — | — | — | — | If exists |
| Custom instructions | — | — | — | — | — | Yes (system prompt) |
| Web tool restriction | Yes | Yes | No | No | No | No |
| External research guidance | No | No | No | Yes | No | Research + Architecture |
| Convention learnings | Yes | Yes | Yes | Yes | No | Yes |
| Cross-project learnings | No | No | No | No | No | Yes (flexible priority) |
| Vault context | Yes (flexible) | Yes (flexible) | Yes (flexible) | Yes (flexible) | No | Yes (flexible) |
| CLAUDE.md | Codex only | Codex only | Codex only | Codex only | Codex only | Codex only |
User Prompt Content
| Section | Worker | Judge | Planner | Researcher | Setup Wizard | Phase Agents |
|---|---|---|---|---|---|---|
| Issue description | Yes (budgeted) | Yes + PR diff | Yes (budgeted) | No (fixed text) | No | Yes |
| Previous attempts | Last 3 | Last 3 | Last 3 | No | No | No |
| Previous findings | Last 5 (budgeted) | Last 5 (budgeted) | Last 5 (budgeted) | No | No | No |
| Conversation history | Budgeted | Budgeted | Budgeted | No | No | Last 5 |
| Related learnings (tactical only) | Up to 25 (budgeted) | Up to 25 (budgeted) | Up to 25 (budgeted) | Yes (inline) | No | No |
| Quality gates | Yes | Yes | Yes | No | No | No |
| Custom instructions | Yes (user prompt) | Yes (user prompt) | Yes (user prompt) | Yes (user prompt) | No | No (in system prompt) |
| PR diff | No | Yes (50KB max) | No | No | No | No |
| Previous phase artifacts | No | No | No | No | No | Yes (budgeted) |
| Phase assignment | No | No | No | No | No | Yes |
| Workspace project list | No | No | No | Yes (multi-project) | No | No |
Data Sources
| User Prompt Section | DB Table / Column | Gathering Logic |
|---|---|---|
| Issue Header | issues (identifier, title, priority, size) | Direct from dispatched issue |
| Description | issues.description | Budgeted via applyContextBudget(); judge dispatch appends PR diff |
| Dependencies | (not yet implemented) | Always passes [] |
| Previous Attempts | agent_runs (attempt, status, durationSeconds, error, output) | .slice(-3) — last 3 runs |
| Previous Findings | issues.findings (JSON column) | .slice(-5) then budgeted |
| Conversation History | issue_comments (author, content, createdAt) | Budgeted (most recent kept) |
| Related Learnings | issues.learnings (JSON on sibling issues) | Split by learning_type via gatherAndSplitLearnings(): conventions (capped at 1,500 chars) go to system prompt; tactical/pattern/gotcha (up to 25) go to user prompt. Both streams are relevance-scored and deduplicated. |
| Quality Gates | projects.config -> ProjectConfig.gates | From project config via getProjectConfig() |
| Custom Instructions | agent_profiles.systemPrompt | Matched by profile type + project ID (falls back to global) |
| PR Diff | pull_requests.diffSnapshot | Most recent PR for issue, truncated at 50,000 chars |
| Previous Phase Artifacts | Files on disk at docs/tickets/{id}/ | gatherPreviousArtifacts(), then applyArtifactBudget() |
Context Omission Per Profile
Symphony filters out irrelevant prompt layers before the budget waterfall to reduce token waste. The omission map is defined in server/orchestrator/prompt/omissions.ts as a static blocklist per profile type. Protected layers (boundary, contract, profile, web_restriction, claude_md) can never be omitted.
| Layer | Worker | Judge | Planner | Researcher | Architect | Scanner |
|---|---|---|---|---|---|---|
| convention_learnings | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
| cross_project_learnings | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ |
| custom_instructions | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ |
| external_research | ✓ | ✗ | ✗ | ✓ | ✓ | ✗ |
| override | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ |
| gate_commands (user) | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ |
| previous_attempts (user) | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ |
| workspace_projects (user) | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ |
✓ = included, ✗ = omitted
How it works: filterOmittedLayers() runs before applySystemPromptBudget(), removing layers whose names match the omission set for the dispatching profile. shouldOmitUserSection() guards inline user prompt sections (gate commands, workspace projects). Debug logging emits omitted layer names per dispatch. Token counts (system + user prompt) are stored in agent_runs.metadata for observability.
Special Cases
Judge dispatch (PR review) — The PR diff is injected into the description by agentDispatcher.ts. It includes branch names, gate results summary, and the diff content truncated at 50,000 characters. Source: pull_requests table, most recent PR for the issue. The judge must call approve_pr or reject_pr. Budget allocations shift when a diff is present (see Token Budget section).
Judge dispatch (phase review) — When a phase uses validation.mode: judge, the orchestrator dispatches a judge with the artifact content (truncated at 30,000 characters) embedded in the user prompt. The judge receives explicit instructions to call approve_phase or reject_phase (NOT approve_pr/reject_pr). The artifact is read from the worktree first, falling back to the project root.
Researcher dispatch — Bypasses buildUserPrompt() entirely. The user prompt is constructed inline in dispatchResearch() with a fixed instruction string. If the workspace has multiple projects, a project list is appended for cross-project awareness. Custom instructions are appended if the researcher profile has a systemPrompt value.
Worker WORKFLOW.md override — If a WORKFLOW.md file exists in the project root, it completely replaces the worker's profile prompt (profiles/worker.md). No other agent type supports this override. CLAUDE.md is still appended (for Codex provider).
Phase contract dispatch — Phase contracts are self-contained prompts (identity + behavior + rules). The contract body IS the prompt. No separate profile layer is used. Custom instructions go in the system prompt (Layer 4), giving them higher authority than in legacy dispatch.
3. Prompt Template Files
Phase Contracts (prompts/phases/)
Phase contracts define the prompt and lifecycle for each pipeline phase. They use YAML frontmatter for metadata and markdown body for the prompt content.
| File | Phase | Dispatch Profile | Artifact | Next Phase |
|---|---|---|---|---|
research.md | research | researcher | docs/tickets/{id}/research.md | architecture |
architecture.md | architecture | architect | docs/tickets/{id}/design.md | grooming |
grooming.md | grooming | planner | docs/tickets/{id}/grooming.md | ready |
ready.md | ready | worker | (code changes) | — |
Frontmatter fields: phase, contract_version, dispatch_profile, artifact_type, artifact_path, required_sections, done_when, validation (mode + structural_checks), next_phase, escalate_if.
Validation modes:
structural— orchestrator checks artifact exists + required sections present, then auto-advancesjudge— structural checks pass, then a judge must review and approve before advancingtrust— structural checks run as warnings only (non-blocking), then auto-advances
Agent Profiles (prompts/profiles/)
Each profile defines the agent's identity (markdown body) and configuration metadata (YAML frontmatter). The frontmatter co-locates structured config with the prompt:
---
name: Worker
type: worker
description: Implements code changes for phase:ready issues
tools: ['*'] # Allowed MCP tools, or ['*'] for all
disallowedTools: [approve_pr] # Tools explicitly denied
model: inherit # Model override or 'inherit' from config
maxTurns: 200 # Max agentic turns (passed as --max-turns to CLI)
background: false # Background-only agent (scanner)
---| File | Agent Type | Background | Description |
|---|---|---|---|
profiles/worker.md | worker | no | Standard implementation dispatch |
profiles/judge.md | judge | no | PR and phase artifact review |
profiles/planner.md | planner | no | Issue decomposition dispatch |
profiles/researcher.md | researcher | no | Deep research on assigned tickets |
profiles/architect.md | architect | no | Implementation design documents |
profiles/intake.md | intake | no | Intake agent for ticket triage |
profiles/scanner.md | scanner | yes | Background codebase scanning |
Profile loading: Profiles are loaded from markdown files at orchestrator boot into a ReadonlyMap<AgentProfileType, AgentProfileDefinition>. The DB agent_profiles table remains an override layer on top of these file-based defaults. The profileSeeder.ts strips frontmatter before inserting the body into the DB.
Model resolution chain (highest priority first):
phaseModels[phase]— per-project per-phase overridecontractModel— phase contract frontmatterprofileDefinitionModel— profile markdown frontmatter (file-based, skipped ifinherit)agentProfileModel— DB-based per-agent-type overrideprojectModel— per-project defaultglobalModel— global default fromsymphony.config.json
Tool consistency check: At boot, the orchestrator compares frontmatter tools lists against the MCP TOOL_REGISTRY. Mismatches are logged as warnings (informational only — TOOL_REGISTRY stays authoritative).
Standalone Prompts
setup-wizard-system-prompt.md— one-shot project configuration agent
Prompt Resolution Priority
For worker/implementer agents:
WORKFLOW.mdin project root (if exists) — completely replaces profile promptprompts/profiles/worker.md(or legacy name viatypePromptMap)
For phase agents:
- Phase contract body from
prompts/phases/<phase>.md - Optional override from
prompts/overrides/<profile>-<phase>.md
CLAUDE.md is always appended for Codex runtime regardless of which base prompt is used.
4. Token Budget System
Symphony uses a token budget system (contextBudget.ts) to prevent unbounded prompt growth. The heuristic is 1 token ~ 4 characters (CHARS_PER_TOKEN = 4).
User Prompt Context Budget
Applied by applyContextBudget() before building the user prompt. Default total budget: 8,000 tokens.
Standard budget (workers, planners — no diff):
| Category | Allocation | Max Tokens | Truncation Rule |
|---|---|---|---|
| Description | 25% | 2,000 | Tail-truncated (keeps end, prepends "...(truncated)") |
| Findings | 20% | 1,600 | Most recent items first, drops oldest |
| Learnings | 15% | 1,200 | Most recent items first, drops oldest |
| Comments | 15% | 1,200 | Most recent items first, drops oldest |
| Previous Output | 25% | 2,000 | Tail-truncated (keeps end) |
Judge budget with diff (when diff field is present):
| Category | Allocation | Max Tokens |
|---|---|---|
| Description | 15% | 1,200 |
| Diff | 25% | 2,000 |
| Findings | 15% | 1,200 |
| Learnings | 10% | 800 |
| Comments | 10% | 800 |
| Previous Output | 25% | 2,000 |
Truncation behavior:
- Text fields (description, previousOutput): kept from the end (tail), so the most recent content survives
- Array fields (findings, learnings, comments): items taken from the end (most recent), dropping oldest when budget exceeded
Phase Artifact Budget
Applied by applyArtifactBudget() when building phase agent user prompts. Default: 4,000 tokens total, split evenly across artifacts.
Fallback strategy (when preferFullText: true, the default):
- Try full artifact text — use if within per-artifact budget
- Fall back to summarized version (headings + first paragraph per section via
summarizeArtifact()) - If summary still too large — truncate the summary
Hard Limits (pre-budget)
These limits are applied before the budget system, as fixed caps:
| Data | Limit | Code Location |
|---|---|---|
| PR diff | 50,000 characters (~12,500 tokens) | agentDispatcher.ts — .slice(0, 50000) |
| Phase artifact (judge review) | 30,000 characters (~7,500 tokens) | agentDispatcher.ts — .slice(0, 30000) |
| Agent output capture | 50KB rolling buffer | agentRunner.ts — MAX_OUTPUT_BYTES |
| Previous attempts | Max 3 runs | agentDispatcher.ts — .slice(-3) |
| Previous findings | Max 5 entries | agentDispatcher.ts — .slice(-5) |
| Related learnings | Max 25 tactical (relevance-scored, deduplicated) | server/learning/processor.ts — gatherAndSplitLearnings() with DEFAULT_MAX_RESULTS |
| Phase comments | Last 5 | dispatchPhaseAgent() — .slice(-5) |
Unbounded Sections (no truncation)
These sections have no budget applied and can grow without limit:
- Custom instructions from agent profiles
- Phase contract body
- Workspace boundary text
Total Context Estimates
| Agent Type | System Prompt | User Prompt | Notes |
|---|---|---|---|
| Worker | ~1,500-6,000 tokens | ~8,000 max (budgeted) | Well within 200K window |
| Judge | ~1,000-5,500 tokens | Variable (large with PR diff) | PR diff is the main budget concern |
| Planner | ~1,000-5,200 tokens | ~8,000 max (budgeted) | Well within 200K window |
| Researcher | ~1,500-5,500 tokens | ~100-300 tokens | Minimal user prompt (no budget needed) |
| Phase Agent | ~2,000-7,000 tokens | Variable (artifact-budgeted) | Phase contract + CLAUDE.md can be large |
5. Custom Instructions Guide
Custom instructions let you tune agent behavior per project without modifying the prompt templates. They are stored in agent_profiles.systemPrompt.
How Custom Instructions Flow
Legacy dispatch (worker, judge, planner, researcher):
Setup Wizard runs -> calls configure_project MCP tool
-> worker_instructions saved to agent_profiles.systemPrompt WHERE type='worker'
-> judge_instructions saved to agent_profiles.systemPrompt WHERE type='judge'
Dispatch happens -> reads agentProfile for target type
-> profile.systemPrompt passed as customInstructions to buildUserPrompt()
-> rendered as "### Project Instructions" section in USER promptPhase dispatch (research, architecture, grooming):
Dispatch happens -> reads agentProfile for dispatch_profile type
-> profile.systemPrompt injected as Layer 4 in SYSTEM prompt
-> rendered as "### Project Instructions" inside system promptThis distinction matters: custom instructions have higher authority in phase dispatch (system prompt) than in legacy dispatch (user prompt).
Writing Effective Worker Instructions
Worker instructions should supplement (not repeat) what the profile prompt already covers. Focus on:
Project conventions the agent cannot infer:
- Use `make test-group GROUP=<namespace>` to run specific test groups
- Database migrations use `safeAlterTable()` — never use Drizzle Kit push
- All API responses must include `request_id` in the response headerImportant gotchas:
- Node 22+ required — run `nvm use 22` before any npm commands
- ESM only — no require(), use import
- Files in app/utils/ are auto-imported by Nuxt — do NOT add explicit importsPatterns to follow:
- Follow existing error handling: throw AppError with HTTP status code
- New API routes need a corresponding test in tests/server/api/
- Use the validation helper from server/utils/validation.ts for input validationWhat NOT to include:
- Generic coding advice (the agent already knows how to code)
- Instructions that contradict the system prompt (system prompt wins in legacy dispatch)
- Very long context dumps (custom instructions are unbounded — be concise)
- Instructions that assume agent memory across sessions (each session is fresh)
Writing Effective Judge Instructions
Judge instructions tell the reviewer what to verify beyond "does the code work":
Stack-specific checks:
- Verify new API routes have corresponding test files
- Check that database queries use parameterized inputs (no SQL injection)
- Ensure new Vue components follow the composables patternProject standards:
- All user-facing strings must use i18n keys, not hardcoded text
- New features must update the OpenAPI spec in engage.architecture
- CSS must use Tailwind utilities, no custom CSS unless unavoidableCustom Instructions: Legacy vs Phase
| Aspect | Legacy Dispatch | Phase Dispatch |
|---|---|---|
| Injected into | User prompt | System prompt |
| Authority | Lower (user-level) | Higher (system-level) |
| Can override base prompt | No | Technically yes (same authority level) |
| Header | ### Project Instructions | ### Project Instructions |
Anti-patterns
- Conflicting instructions: "Don't commit" in custom instructions vs. worker prompt that says commit — system prompt wins in legacy, ambiguous in phase dispatch
- Unbounded length: Custom instructions have no token cap. Keep them concise — every token displaces useful context
- Assuming memory: Each agent session starts fresh. Don't reference "what you did last time"
- Duplicating CLAUDE.md: CLAUDE.md is already read by Claude CLI implicitly. Don't repeat its content in custom instructions
Profile Resolution
Custom instructions are per-project via agent_profiles.projectId:
- Look for a profile matching
(type, projectId) - Fall back to a global profile matching
(type, projectId IS NULL) - If neither exists, no custom instructions are included
6. MCP Tools Available to Agents
Each agent gets an ephemeral MCP server (stdio transport) with all tools. The system prompts guide which tools each agent should use, but nothing enforces this at the tool level — all tools are exposed to every agent type.
Issue Management
| Tool | Purpose | Primary Users |
|---|---|---|
get_issue | Read current issue details | Worker, Judge, Planner |
update_issue_status | Change issue status | All agents |
create_issue | Create a new backlog issue | Worker, Researcher |
create_subtask | Create a child issue | Planner |
list_issues | Query project issues | All agents |
Knowledge Capture
| Tool | Purpose | Primary Users |
|---|---|---|
add_finding | Record a structured finding | Worker, Researcher, Phase agents |
add_learning | Record a reusable pattern | Worker, Researcher |
add_comment | Post a comment on an issue | All agents |
search_learnings | Search knowledge base | All agents |
PR Workflow
| Tool | Purpose | Primary Users |
|---|---|---|
create_pr | Create a PR record with diff snapshot | Worker |
approve_pr | Approve a PR | Judge |
reject_pr | Reject a PR with feedback | Judge |
Phase Lifecycle
| Tool | Purpose | Primary Users |
|---|---|---|
complete_phase | Submit phase completion claim with artifact | Phase agents (researcher, architect, planner) |
approve_phase | Approve a phase artifact | Judge (phase review) |
reject_phase | Reject a phase artifact with feedback | Judge (phase review) |
Coverage & Configuration
| Tool | Purpose | Primary Users |
|---|---|---|
record_coverage | Record research coverage for a file+concept | Scanner |
record_test_health | Record test health metrics (count, bloat detection) | Worker |
configure_project | Set project configuration | Setup Wizard |
Tool source files are in server/mcp/tools/. Each tool connects to SQLite via --issue-id and --db CLI args passed through the MCP config. The researcher uses --project-id instead of issue-based project resolution.
7. Troubleshooting
Agent not following custom instructions
Custom instructions go in the user prompt for legacy dispatch, which Claude treats as lower authority than the system prompt. If custom instructions conflict with the profile prompt, the system prompt wins.
Solutions:
- Rewrite instructions to supplement, not contradict, the profile prompt
- For workers, use
WORKFLOW.mdto fully replace the profile prompt (but you lose all default guidance) - For phase agents, custom instructions are in the system prompt — conflicts are harder to predict
PR diff truncated in judge review
The PR diff is hard-truncated at 50,000 characters in agentDispatcher.ts. For very large PRs, the judge may not see all changes.
Solutions:
- Break large PRs into smaller issues
- Adjust the truncation limit in
agentDispatcher.ts(search for.slice(0, 50000))
Agent using wrong tools
All 17 MCP tools are exposed to all agent types. If an agent misuses tools (e.g., a worker calling approve_pr), the issue is in the system prompt guidance, not tool filtering.
Solution: Edit the relevant prompt template in prompts/profiles/ or prompts/phases/ to add clearer tool usage instructions.
WORKFLOW.md not taking effect
WORKFLOW.md override only works for worker and implementer agent types in legacy dispatch. It must be in the project root directory. If present, it completely replaces the profile prompt — CLAUDE.md is still read by Claude CLI implicitly, but no default worker guidance (commit rules, blocking guidance, etc.) is included unless you add it to WORKFLOW.md.
Researcher not getting issue context
This is by design. Researchers bypass buildUserPrompt() and get a minimal fixed prompt. They do not work on specific issues — they scan the codebase and create new backlog items.
Phase artifact not found by orchestrator
The orchestrator validates phase artifacts by checking:
- Worktree path first (
latestRun.worktreePath + artifact_path) - Falls back to project root (
projectRoot + artifact_path)
If neither location has the file, the phase completion is rejected. Common causes:
- Agent didn't actually write the file (check agent output)
- File was written but not committed (orchestrator checks the filesystem, not git)
- Wrong
artifact_pathpassed tocomplete_phase(must match contract'sartifact_pathtemplate)
Phase stuck in validation loop
If a phase uses validation.mode: judge, the orchestrator needs a phase_verdict finding from a judge before it can advance. The orchestrator adds the issue to needsJudgeReview and dispatches a judge. If no judge is dispatched (all slots full, judge disabled), the phase will wait indefinitely.
Solution: Check orchestrator status page for judge dispatch. Ensure judge slots are available and judge profile is enabled.
Context seems too large
If agents hit token limits or seem to lose context, check:
- Custom instructions length — unbounded, keep concise
- Phase contract body — no truncation
- PR diff — truncated at 50KB but still large (~12,500 tokens)
- CLAUDE.md size — read implicitly by Claude CLI (no truncation applied by Symphony)
The applyContextBudget() system caps the user prompt at ~8,000 tokens for legacy dispatch, but custom instructions and phase contracts sit outside this budget. Consider trimming these if context becomes an issue.
Prompt file not found
Prompt files live in prompts/profiles/<type>.md. The getDefaultPrompt() function maps legacy type names to the correct profile files (implementer → profiles/worker.md, reviewer → profiles/judge.md), so legacy type names still resolve correctly. If you see "Prompt file not found" errors, it likely means a profile file is missing from prompts/profiles/ — check that the expected .md file exists.