HomePost

Running OpenAI Symphony with Claude Code

2026-03-07

Forking OpenAI's coding agent orchestrator Symphony to add a Claude Code backend

What Is Symphony

Symphony is a coding agent orchestrator built by OpenAI. In simple terms, it pulls tasks from an issue tracker (Linear), then automatically assigns them to a coding agent.

Here's how it works:

  1. Polls Linear for issues in Todo status
  2. Creates an isolated workspace (git clone) for each issue
  3. Runs a coding agent (Codex) session in the workspace
  4. The agent implements, opens a PR, awaits review, and merges — all autonomously

The team just manages issues instead of supervising coding agents. You define the agent's behavior in a single WORKFLOW.md file, and Symphony handles the rest.

The problem? It's Codex-only.

Why I Wanted to Use Claude Code

There's a lot of interest in agentic coding workflows these days, and starting from a well-built reference is far better than designing from scratch. Symphony was built by OpenAI themselves, so the architecture and protocol design are solid. Issue polling, workspace isolation, agent lifecycle management, retries and backoff — all neatly documented in a single SPEC.md.

I primarily use Claude Code. I've tried Codex too, but Claude Code fits my workflow better. Having such a well-defined agentic workflow locked to Codex felt like a waste. I wanted to make it work with Claude Code.

On top of that, the original Symphony only supports Linear as an issue tracker, but I don't use Linear. I didn't want to adopt Linear just for this when GitHub Issues works fine. So porting it to use GitHub Issues as a tracker was another goal.

Looking at Symphony's architecture, both seemed feasible. The protocol defined in SPEC.md is generic enough — the only parts tightly coupled to Codex just needed to be extracted.

So I built two things:

  1. symphony-claude: An app-server wrapping Claude Code in Symphony's protocol
  2. Symphony fork: A modified orchestrator supporting multiple backends

symphony-claude: Claude Code's App-Server

Symphony communicates with coding agents via JSON-RPC 2.0. It creates threads, starts turns, and streams results. Codex already had an app-server implementing this protocol, but Claude Code didn't.

sumansid/clode-app-server had a basic implementation wrapping Claude Code in JSON-RPC. I forked it and modified it to be compatible with Symphony's Codex protocol.

Key changes:

  • Parameter compatibility: Accept both threadId/thread_id (Codex uses camelCase)
  • Response format change: { thread_id: "..." }{ thread: { id: "..." } } nested structure
  • Input array support: Handle Codex-style input: [{ type: "text", text: "..." }]
  • Token/cost tracking: Aggregate token usage and cost from stream events, include in turn/completed
  • Event name alignment: turn/errorturn/failed etc., matching the protocol

Installation

bash
brew tap sapsaldog/symphony
brew install symphony-claude

After installation, the symphony-claude command is available. Run it while Claude Code CLI is authenticated.

bash
# Start in WebSocket mode (Symphony connects via this)
symphony-claude start
 
# stdio mode (for piping directly)
symphony-claude

Multi-Backend Symphony Fork

The original Symphony was entirely Codex-centric. Config had a codex: block, event parsing was Codex-format only, tracker was Linear-only. I restructured it to be pluggable.

Coding Agent Abstraction

Defined a CodingAgent behaviour, with Codex and Claude as separate implementations.

text
lib/symphony_elixir/
├── coding_agent.ex          # behaviour definition
├── codex/
│   ├── coding_agent.ex      # Codex implementation
│   ├── config.ex
│   └── event_humanizer.ex
├── claude/
│   ├── coding_agent.ex      # Claude implementation
│   ├── config.ex
│   └── event_humanizer.ex

The CodingAgent behaviour defines three callbacks: start_session, run_turn, stop_session. Whether it's Codex or Claude, as long as the interface is satisfied, Symphony dispatches accordingly.

Issue Tracker Abstraction

Using the same pattern, I created a TrackerConfig behaviour to support GitHub Issues alongside Linear. GitHub Issues uses labels for state management (symphony:todo, symphony:in-progress, etc.).

text
├── linear/
│   ├── client.ex
│   ├── config.ex
│   └── tracker.ex
├── github/
│   ├── client.ex
│   ├── config.ex
│   └── tracker.ex

Linear is paid and team-oriented, which can be overkill for personal projects. With GitHub Issues, anyone can get started immediately.

Module Restructuring

Split the monolithic Config module by backend, and moved event normalization (normalize_event/1) and humanizing into each coding agent module. Over 250 lines of format-sniffing code were eliminated.

Installation & Usage

1. Install Symphony

bash
brew tap sapsaldog/symphony
brew install symphony

If using the Claude backend, also install the app-server:

bash
brew install symphony-claude

2. Authenticate Claude Code CLI

bash
claude auth

3. Write WORKFLOW.md

Create a WORKFLOW.md in your project root. YAML front matter holds configuration, and the body is the agent prompt template.

GitHub Issues + Claude Code example:

yaml
---
github:
  repo: your-org/your-repo
  label_prefix: symphony
tracker:
  active_states:
    - todo
    - in-progress
    - rework
  terminal_states:
    - done
    - cancelled
polling:
  interval_ms: 10000
workspace:
  root: ~/code/symphony-workspaces
hooks:
  after_create: |
    git clone --depth 1 git@github.com:your-org/your-repo.git .
    yarn install
agent:
  max_concurrent_agents: 3
  max_turns: 30
claude:
  command: symphony-claude
---
 
You are working on GitHub Issue `#{{ issue.identifier }}` in `your-org/your-repo`.
 
{% if attempt %}
Continuation context:
 
- This is retry attempt #{{ attempt }} because the issue is still open.
- Resume from the current workspace state instead of restarting from scratch.
{% endif %}
 
Issue context:
Number: #{{ issue.identifier }}
Title: {{ issue.title }}
Labels: {{ issue.labels }}
URL: {{ issue.url }}
 
Description:
{% if issue.body %}
{{ issue.body }}
{% else %}
No description provided.
{% endif %}
 
Instructions:
 
1. This is an unattended orchestration session. Never ask a human to perform follow-up actions.
2. Only stop early for a true blocker (missing required auth/permissions/secrets).
3. Final message must report completed actions and blockers only.
 
Work only in the provided repository copy. Do not touch any other path.

The YAML front matter is configuration, and everything below --- is the prompt template sent to the agent. Variables like {{ issue.identifier }} and {{ issue.title }} are automatically replaced with issue data by Symphony. You can also use conditionals like {% if attempt %} to add retry-specific instructions.

In practice, you'd add detailed sections below this prompt covering codebase conventions, state transition rules, implementation steps, PR feedback handling, workpad templates, etc. The longer and more detailed the prompt, the more consistently the agent behaves. My actual WORKFLOW.md is about 300 lines.

4. Create GitHub Issues Labels

Symphony needs labels to recognize issues:

  • symphony:todo — Issues for Symphony to pick up
  • symphony:in-progress — Currently being worked on
  • symphony:done — Completed

5. Run

bash
symphony /path/to/WORKFLOW.md

A status dashboard appears in the terminal, showing real-time agent activity per issue.

How It Actually Works

  1. Symphony polls GitHub Issues and finds issues with the symphony:todo label
  2. Creates a workspace directory per issue and runs the after_create hook (git clone)
  3. Starts a Claude Code session via symphony-claude
  4. Fills the WORKFLOW.md prompt template with issue data and sends it to the agent
  5. Claude Code analyzes the code, implements changes, and opens a PR
  6. When the agent finishes, the label changes to symphony:done
  7. Moves on to the next issue

Multiple issues can be processed concurrently. Control the concurrency with max_concurrent_agents.

Limitations and Caveats

  • Symphony itself is still experimental. OpenAI explicitly labels it as an "engineering preview." Better suited for personal projects and experiments than production.
  • Claude Code CLI costs money. Since the agent runs autonomously, monitor your token usage. symphony-claude includes per-turn token/cost tracking.
  • WORKFLOW.md prompt quality matters a lot. Be clear about what the agent should and shouldn't do.

Source Code

Both installable via Homebrew tap:

bash
brew tap sapsaldog/symphony
brew install symphony symphony-claude

Symphony's concept is great, but it felt like a waste to have it locked to Codex. With this work, I hope Claude Code users can enjoy the same automation. If you find room for improvement, feel free to open an issue or PR.