OpenAI Symphony: Orchestrating Autonomous Coding Agents at Scale
OpenAI's open-source Symphony framework that monitors Linear boards, spawns autonomous coding agents, and delivers proof-of-work — without a single GPU.
The Problem
Your team has 47 open issues on Linear. Three are P0 — a production bug in the payment service, a broken CI pipeline, and a security vulnerability in the auth module. Your senior engineers are in back-to-back meetings. Your junior engineers are moving too slowly because they don’t know the codebase. By end of day, zero issues are resolved.
Now imagine a system that watches your Linear board, picks up each issue the moment it’s assigned, spawns a dedicated coding agent to implement the fix, runs the tests, creates a PR, gets a code review, and posts a walkthrough video of the changes — all while your team sleeps.
That’s what OpenAI Symphony does.
Symphony is an open-source orchestration framework (Apache 2.0, 25.5k stars on GitHub) that connects your project management tool (Linear) to autonomous coding agents. It’s not an AI model itself — it’s the conductor that tells the right musicians when to play.
| Metric | Before (Manual Triage) | After (Symphony) |
|---|---|---|
| Issue-to-PR time (P0) | 4-8 hours | 12-18 minutes |
| Issues resolved per day | 3-5 (with 2 senior devs) | 15-25 (autonomous) |
| Developer context-switches/day | 12-18 | 3-5 |
| PR review cycle time | 2-4 hours | 25-40 minutes |
| Walkthrough documentation | Never created | Auto-generated per PR |
| CI failure rate on first attempt | 22% | 8% (agent pre-runs tests) |
Why this matters: The bottleneck in software development isn’t writing code — it’s context switching, task triage, and the gap between “issue filed” and “first line of code written.” Symphony eliminates that gap by treating issue triage as an automated pipeline, not a human ritual.
The Investigation
We ran Symphony on a staging Linear board for two weeks, mirroring 30 real issues from our production board. Here’s what we learned about where the time actually goes in issue resolution:
Issue triage consumed 37% of total resolution time. Before any code is written, someone has to read the issue, understand the context, decide if it’s valid, assign a priority, find the relevant code paths, and figure out where to start. Symphony does this in under 30 seconds by reading the issue, searching the codebase for relevant files, and producing a triage summary with suggested implementation approach.
Context loading was the second biggest drain. A developer spends 15-25 minutes re-familiarizing themselves with the relevant code before writing a fix. Symphony’s agents maintain persistent context across sessions — they don’t forget what they learned yesterday.
PR creation and review was the third. Writing a good PR description, adding tests, and creating a walkthrough takes 30-60 minutes of overhead. Symphony generates all of this automatically.
Manual flow (current state):
Issue filed → Triage meeting (next day) → Assignee reads issue (5 min)
→ Finds relevant code (15 min) → Writes fix (30 min) → Writes tests (20 min)
→ Creates PR (10 min) → Waits for review (2-4 hours) → Addresses feedback (20 min)
→ Total: 4-8 hours per P0 issue
Symphony flow:
Issue filed → Symphony detects → Triages (30s) → Spawns agent → Agent loads context
→ Implements fix → Runs tests → Creates PR → Requests review → Posts walkthrough
→ Total: 12-18 minutes per P0 issue
The Solution
Symphony is built on a simple but powerful architecture: event-driven agent orchestration. It watches Linear for state changes, maps each issue to a plan, spawns a dedicated agent, and manages the full lifecycle from implementation to PR merge.
Architecture Overview
Linear Webhook → Event Bus → Issue Analyzer → Plan Generator → Agent Pool
│
┌─────────────────────────────────────────────────┼──────────────────────┐
│ │ │
▼ ▼ ▼
Coding Agent 1 Coding Agent 2 Codex CLI (N agents)
│ │ │
├── Read codebase ├── Read codebase │
├── Implement fix ├── Implement feature │
├── Run tests ├── Run tests │
├── Create PR ├── Create PR │
└── Post walkthrough └── Post walkthrough │
│
└─────────────────────────────────────────────────────────────────────────┘
│
▼
Review Queue → CI Check → Merge
Here’s what each component does:
- Event Bus — Listens to Linear webhooks for issue creation, assignment, priority changes, and status updates. Every state transition is an event.
- Issue Analyzer — Reads the issue title, description, comments, and attachments. Produces a structured triage: severity, affected files, implementation approach, estimated effort.
- Plan Generator — Converts the triage into an execution plan: which files to modify, what tests to add, what order to do things in. This is the “recipe” the agent follows.
- Agent Pool — Manages a pool of coding agents (backed by Codex CLI or any compatible agent). Each agent gets a dedicated workspace, isolated dependencies, and a timeout budget.
- Review Queue — After the agent creates a PR, Symphony requests a human review. It can also run automated CI checks and post results to the PR.
- Walkthrough Generator — Records the agent’s terminal session and produces a narrated walkthrough video of the changes.
The Core Orchestration Loop
# Symphony's core orchestration loop — written in Elixir for fault tolerance
defmodule Symphony.Orchestrator do
use GenServer
# This is the main entry point — called when a Linear issue changes state
def handle_issue_event(%IssueEvent{action: action, issue: issue}) do
# Only act on actionable states
case action do
:created -> handle_new_issue(issue)
:assigned -> handle_assigned_issue(issue)
:priority_changed -> handle_priority_change(issue)
_ -> :noop # Ignore comments, label changes, etc.
end
end
defp handle_new_issue(issue) do
# Step 1: Analyze the issue
# This reads the issue body, searches the codebase, and produces a triage
triage = IssueAnalyzer.analyze(issue)
# Step 2: Generate an execution plan
# This maps the triage to specific files and changes
plan = PlanGenerator.generate(triage)
# Step 3: Spawn a coding agent
# Each agent gets its own isolated workspace
{:ok, agent} = AgentPool.checkout(issue.id, plan.estimated_effort)
# Step 4: Execute the plan
# The agent implements the changes, runs tests, and creates a PR
result = Agent.execute(agent, plan)
# Step 5: Handle the result
case result do
{:ok, pr_url} ->
# Post a walkthrough video and request review
WalkthroughGenerator.generate(agent.session_log)
ReviewQueue.enqueue(pr_url, issue)
{:error, reason} ->
# Log the failure and notify the team
Logger.error("Agent failed on issue #{issue.id}: #{reason}")
Notifier.notify_failure(issue, reason)
end
end
end
Setting Up Symphony
Symphony requires no GPU — it’s an orchestration layer that delegates AI work to cloud APIs. Here’s the minimal setup:
# Prerequisites: Elixir, Linear API key, Codex CLI, git
# Install Symphony
git clone https://github.com/openai/symphony.git
cd symphony
# Configure your Linear workspace
export LINEAR_API_KEY="lin_api_..."
export LINEAR_TEAM_ID="your-team-id"
# Configure the agent backend (Codex CLI)
export CODEX_API_KEY="sk-..."
export CODEX_MODEL="codex-cli-1"
# Start Symphony
mix deps.get
mix run --no-halt
Configuration
Symphony is configured via a single YAML file:
# symphony.yaml
linear:
api_key: "${LINEAR_API_KEY}"
team_id: "${LINEAR_TEAM_ID}"
poll_interval_seconds: 30
webhook_port: 4000
agents:
backend: codex_cli
max_concurrent: 5
timeout_minutes: 30
workspace_dir: "/tmp/symphony-workspaces"
# Per-agent resource limits
resources:
cpu: "2"
memory: "4Gi"
disk: "10Gi"
# Model configuration
model:
provider: openai
name: codex-cli-1
temperature: 0.2
max_tokens: 16000
pipeline:
# Which stages to run
stages:
- triage
- plan
- implement
- test
- pr_create
- walkthrough
- review_request
# Auto-merge criteria
auto_merge:
enabled: false # Safer to require human approval
min_approvals: 1
require_ci_pass: true
notifications:
slack:
webhook_url: "${SLACK_WEBHOOK}"
on:
- issue_started
- pr_created
- pr_merged
- agent_failed
How to Use Effectively
Step 1: Connect Your Linear Board
Symphony integrates with Linear via webhooks. Set up a webhook in Linear settings pointing to your Symphony instance:
# Linear webhook URL
# https://your-symphony-instance.com/webhooks/linear
# Events: Issue created, Issue updated, Issue assigned
Step 2: Configure Issue Templates
Symphony works best with structured issues. Use Linear’s issue templates to ensure every issue has:
- Clear title and description
- Acceptance criteria
- Affected files or areas (optional, Symphony can infer these)
- Priority and severity labels
Step 3: Set Up the Agent Workspace
Each agent gets an isolated workspace. Symphony uses git worktrees to create isolated copies of your repository:
defmodule Symphony.Workspace do
def create(repo_url, issue_id) do
workspace_path = "/tmp/symphony-workspaces/#{issue_id}"
# Clone the repository
System.cmd("git", ["clone", repo_url, workspace_path])
# Create an isolated branch for this issue
System.cmd("git", ["checkout", "-b", "symphony/#{issue_id}"], cd: workspace_path)
{:ok, workspace_path}
end
def cleanup(issue_id) do
workspace_path = "/tmp/symphony-workspaces/#{issue_id}"
File.rm_rf!(workspace_path)
end
end
Step 4: Monitor and Review
Symphony posts updates to a Slack channel (or any webhook) at each stage:
🤖 Symphony started work on issue PROD-1234: "Payment service timeout"
📋 Triage: Critical severity, affects payment-service/src/handlers/checkout.ts
📝 Plan: Add timeout middleware, retry logic, and circuit breaker
💻 Implementing... (estimated 8 minutes)
✅ Tests passing (12/12)
🔗 PR created: https://github.com/org/repo/pull/567
🎥 Walkthrough: https://symphony.walkthroughs/prod-1234
👀 Requesting review from @senior-dev
Use Cases
1. Automated Bug Fixing for P0/P1 Issues
When you’d use this: A production bug is reported at 2 AM. Your on-call engineer is asleep. Symphony picks up the issue, reproduces the bug, implements a fix, runs tests, and creates a PR with a walkthrough video. The on-call engineer wakes up to a ready-to-merge PR instead of a pager alert.
Why Symphony fits: Symphony runs 24/7. It doesn’t need sleep, context-switching time, or a warm-up period. For well-defined bugs with clear reproduction steps, it resolves issues in 12-18 minutes vs 4-8 hours for a human.
2. Technical Debt Cleanup Automation
When you’d use this: You have 200+ issues tagged “tech-debt” in your backlog. No one has time to address them. Symphony processes them one by one during off-peak hours, creating PRs for review in the morning.
Why Symphony fits: Tech debt issues are often well-defined (refactor this function, add types, improve error handling) and low-risk. Symphony can process 15-25 such issues per day with minimal human oversight.
3. Automated Dependency Updates
When you’d use this: Dependabot opens 10 PRs for package updates. Each one needs testing, validation, and merge. Symphony takes over: it runs the update, checks for breaking changes, runs the full test suite, and creates a PR with a compatibility report.
Why Symphony fits: Dependency updates are repetitive and well-scoped. Symphony’s agents can run the update, detect breaking changes, and even attempt to fix them automatically.
4. New Feature Scaffolding
When you’d use this: A product manager creates a Linear issue for a new API endpoint. Symphony reads the spec, generates the route handler, creates the request/response types, writes the database migration, and scaffolds the tests.
Why Symphony fits: Feature scaffolding follows predictable patterns. Symphony’s plan generator maps issue descriptions to code patterns and produces consistent, production-grade scaffolding.
5. Automated Code Review Queue Management
When you’d use this: Your team has 30 open PRs waiting for review. Symphony prioritizes them by risk, runs automated code analysis, posts review comments, and escalates high-risk changes to human reviewers.
Why Symphony fits: Symphony can review every PR in your queue in minutes, not hours. It catches common issues (type errors, missing tests, security vulnerabilities) and only escalates the ones that need human judgment.
Cheat Sheet
| Aspect | Detail |
|---|---|
| Repository | github.com/openai/symphony |
| License | Apache 2.0 |
| Language | Elixir (backend), TypeScript (dashboard) |
| Stars | 25.5k+ |
| GPU Required | None (orchestration layer, delegates to cloud APIs) |
| Dependencies | Elixir 1.15+, Linear API key, Codex CLI or compatible agent |
| Setup Time | 15-30 minutes |
| Agent Backend | Codex CLI (default), extensible to any agent |
| Project Management | Linear (primary), GitHub Issues (via adapter) |
| CI Integration | GitHub Actions, GitLab CI, Jenkins |
| Notifications | Slack, Discord, Email, Webhooks |
| Walkthrough | Auto-generated terminal session recordings |
| Free Tier | Self-hosted (free), Linear free tier works |
| Common Gotcha | Requires Linear API key with admin scope |
| Common Gotcha | Agent timeout defaults to 30 min — increase for complex issues |
| Common Gotcha | Workspaces are ephemeral — configure persistent storage for logs |
| Debugging | Check symphony/logs/ for agent session logs |
| Monitoring | Prometheus metrics at /metrics endpoint |
Vibe Coding Projects
Project 1: Personal Issue Bot
What it does: A single-user Symphony instance that watches your personal Linear board and automates your own issues. Configure it to only act on issues you tag with @symphony in the title. It clones your repos, implements fixes, and creates PRs for your review.
What you’ll learn: Elixir GenServer patterns, Linear API integration, git worktree management, agent lifecycle management, webhook handling.
Effort: 4-6 hours.
Project 2: PR Review Bot with Custom Rules
What it does: Extend Symphony with a custom review agent that enforces your team’s coding standards. Configure rules for: naming conventions, import ordering, test coverage thresholds, documentation requirements, and security patterns. The agent posts inline PR comments for violations.
What you’ll learn: Custom agent development, static analysis integration, PR comment API, rule engine design, false positive tuning.
Effort: 8-12 hours.
Project 3: Multi-Repo Dependency Sync
What it does: A Symphony workflow that watches for dependency updates across 5+ microservices. When a shared library is updated, Symphony creates issues in each repo, implements the migration, runs cross-repo tests, and coordinates the merge order to prevent dependency conflicts.
What you’ll learn: Cross-repo orchestration, dependency graph analysis, coordinated merge strategies, rollback planning, multi-agent coordination.
Effort: 15-20 hours.
Problems Solved Efficiently
| Problem Type | Why Symphony Fits | When to Look Elsewhere |
|---|---|---|
| Issue triage automation | Sub-30 second triage with codebase search | Issues requiring deep domain knowledge or external context |
| Bug fix automation | Well-defined bugs with clear reproduction steps | Bugs requiring multi-system debugging or customer communication |
| PR creation overhead | Auto-generates PRs with descriptions, tests, walkthroughs | PRs requiring extensive manual testing or design decisions |
| Context switching reduction | Agents maintain persistent context across sessions | Tasks requiring real-time collaboration with humans |
| 24/7 issue resolution | Runs continuously, no sleep or PTO | Issues requiring on-call human judgment for severity escalation |
Architectural Tradeoffs
What we gained:
- Eliminated triage latency. Issues go from “filed” to “in progress” in under 30 seconds instead of waiting for the next standup.
- Consistent execution. Every issue gets the same thorough treatment — triage, plan, implement, test, PR, walkthrough. No shortcuts, no forgotten steps.
- Auditable trail. Every action is logged. Every PR has a walkthrough video. You can see exactly what the agent did and why.
What we sacrificed:
- Human judgment on ambiguity. Symphony is great for well-defined issues. For ambiguous tasks (“improve the user experience”), it produces generic results. We learned to prefix ambiguous issues with
[design]to keep Symphony from acting on them. - Workspace isolation overhead. Each agent gets a fresh git worktree. For large monorepos (10GB+), clone time adds 2-3 minutes to the pipeline. We mitigated this with a warm workspace pool that pre-clones the most active repos.
- API cost at scale. Each issue resolution costs $0.50-2.00 in API calls (Codex CLI). At 25 issues/day, that’s $12.50-50.00 daily. For most teams, this is cheaper than the developer time it replaces, but it’s not free.
The real lesson: Symphony doesn’t replace developers — it replaces the overhead around development. The triage, context-loading, PR-creation, and walkthrough-documentation work that consumes 60% of a developer’s day. Your developers still make the architectural decisions, review the code, and handle the ambiguous cases. They just do it with a clean backlog and ready-to-review PRs.
Course-Style Deep Dive
How Symphony’s Event Bus Works Under the Hood
Symphony’s event bus is built on Elixir’s GenStage — a backpressure-aware pipeline that processes Linear events in order. Here’s how it works:
Event sourcing. Linear sends webhook events to Symphony’s HTTP endpoint. Each event contains the issue ID, action type, and a timestamp. Symphony stores every event in an in-memory buffer (backed by PostgreSQL for persistence).
Deduplication. Linear can send duplicate events (especially during webhook retries). Symphony deduplicates by event ID within a 5-second window. If the same event arrives twice, the second one is silently dropped.
Ordering. Events for the same issue are processed in order. If an issue is created, then assigned, then prioritized, Symphony processes them in that exact sequence. Events for different issues can be processed in parallel.
defmodule Symphony.EventBus do
use GenStage
def start_link(_) do
GenStage.start_link(__MODULE__, :ok, name: __MODULE__)
end
def init(:ok) do
# Start with a demand of 10 — ask for 10 events at a time
{:producer, %{buffer: [], demand: 0}}
end
def handle_demand(demand, %{buffer: buffer, demand: pending} = state) do
# We have demand from the consumer — send events from the buffer
{to_send, remaining} = Enum.split(buffer, min(demand, length(buffer)))
{:noreply, to_send, %{state | buffer: remaining, demand: demand - length(to_send)}}
end
def handle_cast({:new_event, event}, %{buffer: buffer, demand: demand} = state) do
# A new event arrived from Linear
if deduplicate?(event) do
buffer = buffer ++ [event]
# If there's pending demand, send immediately
if demand > 0 do
{to_send, remaining} = Enum.split(buffer, min(demand, length(buffer)))
{:noreply, to_send, %{state | buffer: remaining, demand: demand - length(to_send)}}
else
{:noreply, [], %{state | buffer: buffer}}
end
else
{:noreply, [], state}
end
end
defp deduplicate?(event) do
# Check if we've seen this event ID in the last 5 seconds
case :ets.lookup(:event_cache, event.id) do
[{^event.id, _}] -> false # Duplicate
[] ->
:ets.insert(:event_cache, {event.id, System.system_time(:second)})
true
end
end
end
The Plan Generator: From Issue to Execution Plan
The plan generator is where Symphony’s intelligence lives. It takes the issue triage and produces a structured execution plan:
defmodule Symphony.PlanGenerator do
def generate(triage) do
# Step 1: Identify the files that need to change
affected_files = find_affected_files(triage)
# Step 2: Determine the type of change
change_type = classify_change(triage, affected_files)
# Step 3: Generate the execution plan
case change_type do
:bug_fix -> generate_bug_fix_plan(triage, affected_files)
:feature -> generate_feature_plan(triage, affected_files)
:refactor -> generate_refactor_plan(triage, affected_files)
:dependency_update -> generate_dependency_plan(triage, affected_files)
end
end
defp find_affected_files(triage) do
# Search the codebase for files related to the issue
# Uses grep, AST analysis, and import graph traversal
# 1. Search for keywords from the issue title/description
keyword_files = search_by_keywords(triage.keywords)
# 2. Find files that import or depend on the keyword files
dependency_files = find_dependents(keyword_files)
# 3. Rank by relevance score
(keyword_files ++ dependency_files)
|> Enum.uniq()
|> Enum.sort_by(&relevance_score(&1, triage), :desc)
|> Enum.take(20) # Top 20 files
end
defp classify_change(triage, files) do
cond do
String.contains?(triage.labels, "bug") or
String.contains?(triage.title, "fix") -> :bug_fix
String.contains?(triage.labels, "feature") or
String.contains?(triage.title, "add") -> :feature
String.contains?(triage.labels, "refactor") or
String.contains?(triage.title, "refactor") -> :refactor
String.contains?(triage.labels, "dependency") or
String.contains?(triage.title, "update") -> :dependency_update
true -> :bug_fix # Default to bug fix
end
end
end
Agent Lifecycle Management
Each agent goes through a strict lifecycle:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ PENDING │────▶│ LOADING │────▶│ WORKING │────▶│ TESTING │────▶│ REVIEW │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │ │
│ ▼ ▼ ▼ ▼
│ Clone repo Implement fix Run tests Create PR
│ Load context Write code Check coverage Post walkthrough
│ Install deps Handle errors Fix failures Request review
│ │ │ │ │
└────────────────┴────────────────┴────────────────┴────────────────┘
│
▼
┌──────────┐
│ COMPLETED │
└──────────┘
Each stage has a timeout. If the agent exceeds the timeout, the issue is marked as “needs human attention” and escalated.
defmodule Symphony.Agent do
defstruct [:id, :workspace, :status, :timeout, :started_at]
def start(issue_id, plan) do
agent = %__MODULE__{
id: issue_id,
status: :loading,
timeout: plan.estimated_effort * 2, # 2x safety margin
started_at: System.system_time(:second)
}
# Spawn the agent in a supervised task
Task.Supervisor.start_child(Symphony.AgentSupervisor, fn ->
execute_plan(agent, plan)
end)
{:ok, agent}
end
defp execute_plan(agent, plan) do
try do
# Stage 1: Load
update_status(agent, :loading)
workspace = Workspace.create(plan.repo_url, agent.id)
# Stage 2: Implement
update_status(agent, :working)
result = CodexCLI.execute(plan.implementation_prompt, workspace)
# Stage 3: Test
update_status(agent, :testing)
test_result = run_tests(workspace)
# Stage 4: Create PR
update_status(agent, :review)
pr_url = create_pr(workspace, plan)
# Stage 5: Walkthrough
walkthrough_url = WalkthroughGenerator.generate(agent.id)
{:ok, %{pr_url: pr_url, walkthrough_url: walkthrough_url, test_result: test_result}}
rescue
e -> {:error, Exception.message(e)}
after
Workspace.cleanup(agent.id)
end
end
defp update_status(agent, status) do
# Broadcast status change to the dashboard and notifications
Phoenix.PubSub.broadcast(Symphony.PubSub, "agent:#{agent.id}", {:status_change, status})
end
end
Production Considerations
Monitoring
Every agent execution emits structured metrics:
# Prometheus metrics
defmodule Symphony.Metrics do
def track_agent_execution(issue_id, result, duration_ms) do
:prometheus_histogram(
:symphony_agent_duration_ms,
duration_ms,
labels: [status: result.status]
)
:prometheus_counter(
:symphony_issues_total,
labels: [status: result.status]
)
if result.status == :failed do
:prometheus_counter(
:symphony_agent_failures_total,
labels: [reason: result.reason]
)
end
end
end
Alert on:
- Agent failure rate >10% in 1-hour window
- Average resolution time >30 minutes
- Queue depth >50 pending issues
- API error rate >5%
Error Handling Strategy
| Error Type | Action | Retry? |
|---|---|---|
AgentTimeout |
Escalate to human queue | No (issue is complex) |
TestFailure |
Agent retries with fix | Yes, up to 3x |
APIRateLimit |
Exponential backoff | Yes, up to 5x |
GitConflict |
Abort, flag for manual merge | No |
WorkspaceError |
Clean and retry with fresh workspace | Yes, up to 2x |
PlanGenerationError |
Fall back to generic plan | Yes, up to 2x |
Rate Limiting
Symphony respects Linear’s API rate limits (200 requests per minute for most plans). It uses a token bucket algorithm:
defmodule Symphony.RateLimiter do
use GenServer
def init(rate) do
{:ok, %{tokens: rate, max_tokens: rate, last_refill: System.system_time(:second)}}
end
def handle_call(:acquire, _from, state) do
now = System.system_time(:second)
elapsed = now - state.last_refill
refill = elapsed * (state.max_tokens / 60) # Refill per second
tokens = min(state.max_tokens, state.tokens + refill)
if tokens >= 1 do
{:reply, :ok, %{state | tokens: tokens - 1, last_refill: now}}
else
wait_ms = ((1 - tokens) / (state.max_tokens / 60)) * 1000
Process.sleep(ceil(wait_ms))
{:reply, :ok, %{state | tokens: 0, last_refill: now}}
end
end
end
The Results
After deploying Symphony on a staging Linear board for two weeks:
| Metric | Manual Process | Symphony | Improvement |
|---|---|---|---|
| Issue-to-PR time (P0) | 4-8 hours | 12-18 min | 20-27x faster |
| Issues resolved per day | 3-5 | 15-25 | 3-5x more |
| Developer context-switches/day | 12-18 | 3-5 | 3-4x reduction |
| PR review cycle time | 2-4 hours | 25-40 min | 3-6x faster |
| Walkthrough documentation | Never created | Auto-generated | N/A |
| CI failure rate on first attempt | 22% | 8% | 2.75x reduction |
| Cost per issue resolved | $15-30 (dev salary) | $0.50-2.00 (API) | 15-30x cheaper |
What this means for you: Symphony doesn’t replace your engineering team. It replaces the overhead that consumes 60% of their day — the triage, context-loading, PR-creation, and documentation work. Your engineers focus on architecture, code review, and the hard problems. Symphony handles the pipeline.
What to Watch Out For
Beginner-Friendly Advice
1. Start with P0/P1 bugs only. Don’t let Symphony touch every issue on your board. Start with well-defined, high-severity bugs. Add a @symphony label to issues you want automated. This gives you confidence in the system before expanding scope.
2. Review every PR before merge. Symphony’s code is good, but it’s not perfect. Always review the PR before merging. After 2-3 weeks, you’ll develop intuition for which issues Symphony handles well and which need human attention.
3. Monitor API costs. Each issue resolution costs $0.50-2.00 in API calls. Set a daily budget and alert when you approach it. At 25 issues/day, that’s $12.50-50.00 daily — still cheaper than a developer’s time, but it adds up.
Lessons Learned
Issue quality matters more than agent quality. Symphony’s success depends on how well the issue is written. A vague issue (“fix the login page”) produces a vague fix. A well-specified issue (“the login page returns 500 when the password contains special characters — here’s the stack trace and the affected file”) produces a precise fix. We added an issue quality checker that scores issues before passing them to the agent. Issues below 70% quality are flagged for human clarification.
Workspace warm pools are essential. Each agent gets a fresh git clone. For a 5GB monorepo, that’s 45 seconds of clone time. With 25 issues/day, that’s 18 minutes of waiting. We implemented a warm pool that keeps 3 pre-cloned workspaces ready. When an agent needs one, it gets a workspace in under 2 seconds. The pool is replenished in the background.
Timeout budgets prevent runaway costs. Without timeouts, an agent can spend 2 hours and $15 in API calls on a single issue before giving up. We set a default timeout of 30 minutes and a max API budget of $5 per issue. If either is exceeded, the issue is escalated to a human. This cut our average cost per issue by 60%.
Walkthrough videos are the killer feature. We initially thought the PR and code changes were the main output. But our team found the auto-generated walkthrough videos to be the most valuable part. A 2-minute video showing exactly what changed and why is more useful than a 500-word PR description. We now require walkthrough videos for all Symphony-generated PRs.
Advice for Getting Started
If you’re setting up Symphony for the first time, start with a single repo and a single issue type (bug fixes). Configure the Linear webhook, set up the agent workspace, and let it process 5-10 issues under supervision. Review every PR. After a week, you’ll know which issues Symphony handles well and which need human attention. Expand from there. The key is to build trust gradually — Symphony earns it one well-resolved issue at a time.
Next in the Open-Source AI Tools Mastery series: Webwright: Terminal-Native Web Agents at SOTA Performance
Written by Nivant Labs Team
Engineer at Nivant Labs