·18 min read

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

NL

Written by Nivant Labs Team

Engineer at Nivant Labs

Share this post