Skip to content

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 text
  • server/orchestrator/agentRunner.ts — launches Claude CLI with assembled prompts
  • server/orchestrator/contextBudget.ts — token budget system for user prompt sections
  • server/learning/processor.ts — gathers, scores, deduplicates, and splits learnings into conventions vs tactical
  • server/orchestrator/contractParser.ts — parses phase contract frontmatter
  • server/orchestrator/frontmatterParser.ts — shared YAML frontmatter parser (used by contracts and profiles)
  • server/orchestrator/profileParser.ts — parses agent profile definitions from markdown with YAML frontmatter
  • prompts/profiles/*.md — agent type prompt templates with YAML frontmatter metadata
  • prompts/phases/*.md — phase contract prompts

Table of Contents

  1. Prompt Assembly Pipeline
  2. Context Flow Per Agent Type
  3. Prompt Template Files
  4. Token Budget System
  5. Custom Instructions Guide
  6. MCP Tools Available to Agents
  7. 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:

  1. getWorkspaceBoundary(worktreePath, identifier, branchName) returns workspace boundary text (includes FIRST ACTION: pwd verification)
  2. buildSystemPrompt({ projectRoot, promptsDir, agentType, vaultContextLayer }) returns profile + web restriction + external research + conventions + vault context
  3. Concatenation: workspaceBoundary + '\n\n---\n\n' + baseSystemPrompt
  4. Written to temp file via writeSystemPromptFile()
  5. 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):

  1. gatherAndSplitLearnings() collects all learnings from sibling issues in the same project
  2. Learnings are deduplicated (substring containment — longer pattern wins) and relevance-scored
  3. Learnings are split by learning_type:
    • convention type → injected into system prompt as Layer 4 (capped at 1,500 characters via CONVENTION_CHAR_CAP)
    • All other types (pattern, gotcha, unset) → passed to user prompt as "Related Learnings" (up to 25 items via DEFAULT_MAX_RESULTS)

Relevance scoring weights (applied to both convention and tactical learnings):

FactorWeightAlgorithm
Label overlap35%Jaccard similarity between issue labels and source issue labels
Path match30%Substring containment of applies_to paths in issue text
Keyword overlap20%Jaccard similarity of tokenized text (stop words filtered)
Recency15%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_type field 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:

bash
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-cli provider): CLAUDE.md is read implicitly by the CLI — no injection needed
  • For Codex CLI (codex-cli provider): CLAUDE.md must be injected into the system prompt explicitly (inject mode)
  • 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.md file in the project root

2. Context Flow Per Agent Type

System Prompt Content

LayerWorkerJudgePlannerResearcherSetup WizardPhase Agents
Workspace boundaryYesYesYesYesYesYes
Profile promptprofiles/worker.mdprofiles/judge.mdprofiles/planner.mdprofiles/researcher.mdsetup-wizard-system-prompt.md
Phase contractphases/<phase>.md
Profile-phase overrideIf exists
Custom instructionsYes (system prompt)
Web tool restrictionYesYesNoNoNoNo
External research guidanceNoNoNoYesNoResearch + Architecture
Convention learningsYesYesYesYesNoYes
Cross-project learningsNoNoNoNoNoYes (flexible priority)
Vault contextYes (flexible)Yes (flexible)Yes (flexible)Yes (flexible)NoYes (flexible)
CLAUDE.mdCodex onlyCodex onlyCodex onlyCodex onlyCodex onlyCodex only

User Prompt Content

SectionWorkerJudgePlannerResearcherSetup WizardPhase Agents
Issue descriptionYes (budgeted)Yes + PR diffYes (budgeted)No (fixed text)NoYes
Previous attemptsLast 3Last 3Last 3NoNoNo
Previous findingsLast 5 (budgeted)Last 5 (budgeted)Last 5 (budgeted)NoNoNo
Conversation historyBudgetedBudgetedBudgetedNoNoLast 5
Related learnings (tactical only)Up to 25 (budgeted)Up to 25 (budgeted)Up to 25 (budgeted)Yes (inline)NoNo
Quality gatesYesYesYesNoNoNo
Custom instructionsYes (user prompt)Yes (user prompt)Yes (user prompt)Yes (user prompt)NoNo (in system prompt)
PR diffNoYes (50KB max)NoNoNoNo
Previous phase artifactsNoNoNoNoNoYes (budgeted)
Phase assignmentNoNoNoNoNoYes
Workspace project listNoNoNoYes (multi-project)NoNo

Data Sources

User Prompt SectionDB Table / ColumnGathering Logic
Issue Headerissues (identifier, title, priority, size)Direct from dispatched issue
Descriptionissues.descriptionBudgeted via applyContextBudget(); judge dispatch appends PR diff
Dependencies(not yet implemented)Always passes []
Previous Attemptsagent_runs (attempt, status, durationSeconds, error, output).slice(-3) — last 3 runs
Previous Findingsissues.findings (JSON column).slice(-5) then budgeted
Conversation Historyissue_comments (author, content, createdAt)Budgeted (most recent kept)
Related Learningsissues.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 Gatesprojects.config -> ProjectConfig.gatesFrom project config via getProjectConfig()
Custom Instructionsagent_profiles.systemPromptMatched by profile type + project ID (falls back to global)
PR Diffpull_requests.diffSnapshotMost recent PR for issue, truncated at 50,000 chars
Previous Phase ArtifactsFiles 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.

LayerWorkerJudgePlannerResearcherArchitectScanner
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.

FilePhaseDispatch ProfileArtifactNext Phase
research.mdresearchresearcherdocs/tickets/{id}/research.mdarchitecture
architecture.mdarchitecturearchitectdocs/tickets/{id}/design.mdgrooming
grooming.mdgroomingplannerdocs/tickets/{id}/grooming.mdready
ready.mdreadyworker(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-advances
  • judge — structural checks pass, then a judge must review and approve before advancing
  • trust — 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:

yaml
---
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)
---
FileAgent TypeBackgroundDescription
profiles/worker.mdworkernoStandard implementation dispatch
profiles/judge.mdjudgenoPR and phase artifact review
profiles/planner.mdplannernoIssue decomposition dispatch
profiles/researcher.mdresearchernoDeep research on assigned tickets
profiles/architect.mdarchitectnoImplementation design documents
profiles/intake.mdintakenoIntake agent for ticket triage
profiles/scanner.mdscanneryesBackground 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):

  1. phaseModels[phase] — per-project per-phase override
  2. contractModel — phase contract frontmatter
  3. profileDefinitionModel — profile markdown frontmatter (file-based, skipped if inherit)
  4. agentProfileModel — DB-based per-agent-type override
  5. projectModel — per-project default
  6. globalModel — global default from symphony.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:

  1. WORKFLOW.md in project root (if exists) — completely replaces profile prompt
  2. prompts/profiles/worker.md (or legacy name via typePromptMap)

For phase agents:

  1. Phase contract body from prompts/phases/<phase>.md
  2. 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):

CategoryAllocationMax TokensTruncation Rule
Description25%2,000Tail-truncated (keeps end, prepends "...(truncated)")
Findings20%1,600Most recent items first, drops oldest
Learnings15%1,200Most recent items first, drops oldest
Comments15%1,200Most recent items first, drops oldest
Previous Output25%2,000Tail-truncated (keeps end)

Judge budget with diff (when diff field is present):

CategoryAllocationMax Tokens
Description15%1,200
Diff25%2,000
Findings15%1,200
Learnings10%800
Comments10%800
Previous Output25%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):

  1. Try full artifact text — use if within per-artifact budget
  2. Fall back to summarized version (headings + first paragraph per section via summarizeArtifact())
  3. If summary still too large — truncate the summary

Hard Limits (pre-budget)

These limits are applied before the budget system, as fixed caps:

DataLimitCode Location
PR diff50,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 capture50KB rolling bufferagentRunner.tsMAX_OUTPUT_BYTES
Previous attemptsMax 3 runsagentDispatcher.ts.slice(-3)
Previous findingsMax 5 entriesagentDispatcher.ts.slice(-5)
Related learningsMax 25 tactical (relevance-scored, deduplicated)server/learning/processor.tsgatherAndSplitLearnings() with DEFAULT_MAX_RESULTS
Phase commentsLast 5dispatchPhaseAgent().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 TypeSystem PromptUser PromptNotes
Worker~1,500-6,000 tokens~8,000 max (budgeted)Well within 200K window
Judge~1,000-5,500 tokensVariable (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 tokensMinimal user prompt (no budget needed)
Phase Agent~2,000-7,000 tokensVariable (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 prompt

Phase 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 prompt

This 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 header

Important 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 imports

Patterns 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 validation

What 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 pattern

Project 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 unavoidable

Custom Instructions: Legacy vs Phase

AspectLegacy DispatchPhase Dispatch
Injected intoUser promptSystem prompt
AuthorityLower (user-level)Higher (system-level)
Can override base promptNoTechnically 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:

  1. Look for a profile matching (type, projectId)
  2. Fall back to a global profile matching (type, projectId IS NULL)
  3. 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

ToolPurposePrimary Users
get_issueRead current issue detailsWorker, Judge, Planner
update_issue_statusChange issue statusAll agents
create_issueCreate a new backlog issueWorker, Researcher
create_subtaskCreate a child issuePlanner
list_issuesQuery project issuesAll agents

Knowledge Capture

ToolPurposePrimary Users
add_findingRecord a structured findingWorker, Researcher, Phase agents
add_learningRecord a reusable patternWorker, Researcher
add_commentPost a comment on an issueAll agents
search_learningsSearch knowledge baseAll agents

PR Workflow

ToolPurposePrimary Users
create_prCreate a PR record with diff snapshotWorker
approve_prApprove a PRJudge
reject_prReject a PR with feedbackJudge

Phase Lifecycle

ToolPurposePrimary Users
complete_phaseSubmit phase completion claim with artifactPhase agents (researcher, architect, planner)
approve_phaseApprove a phase artifactJudge (phase review)
reject_phaseReject a phase artifact with feedbackJudge (phase review)

Coverage & Configuration

ToolPurposePrimary Users
record_coverageRecord research coverage for a file+conceptScanner
record_test_healthRecord test health metrics (count, bloat detection)Worker
configure_projectSet project configurationSetup 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.md to 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:

  1. Worktree path first (latestRun.worktreePath + artifact_path)
  2. 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_path passed to complete_phase (must match contract's artifact_path template)

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:

  1. Custom instructions length — unbounded, keep concise
  2. Phase contract body — no truncation
  3. PR diff — truncated at 50KB but still large (~12,500 tokens)
  4. 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 (implementerprofiles/worker.md, reviewerprofiles/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.