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:
- Polls Linear for issues in
Todostatus - Creates an isolated workspace (git clone) for each issue
- Runs a coding agent (Codex) session in the workspace
- 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:
- symphony-claude: An app-server wrapping Claude Code in Symphony's protocol
- 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/error→turn/failedetc., matching the protocol
Installation
brew tap sapsaldog/symphony
brew install symphony-claudeAfter installation, the symphony-claude command is available. Run it while Claude Code CLI is authenticated.
# Start in WebSocket mode (Symphony connects via this)
symphony-claude start
# stdio mode (for piping directly)
symphony-claudeMulti-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.
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.exThe 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.).
├── linear/
│ ├── client.ex
│ ├── config.ex
│ └── tracker.ex
├── github/
│ ├── client.ex
│ ├── config.ex
│ └── tracker.exLinear 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
brew tap sapsaldog/symphony
brew install symphonyIf using the Claude backend, also install the app-server:
brew install symphony-claude2. Authenticate Claude Code CLI
claude auth3. 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:
---
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 upsymphony:in-progress— Currently being worked onsymphony:done— Completed
5. Run
symphony /path/to/WORKFLOW.mdA status dashboard appears in the terminal, showing real-time agent activity per issue.
How It Actually Works
- Symphony polls GitHub Issues and finds issues with the
symphony:todolabel - Creates a workspace directory per issue and runs the
after_createhook (git clone) - Starts a Claude Code session via
symphony-claude - Fills the WORKFLOW.md prompt template with issue data and sends it to the agent
- Claude Code analyzes the code, implements changes, and opens a PR
- When the agent finishes, the label changes to
symphony:done - 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-claudeincludes 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
- Symphony fork: github.com/sapsaldog/symphony
- symphony-claude: github.com/sapsaldog/claude-app-server
Both installable via Homebrew tap:
brew tap sapsaldog/symphony
brew install symphony symphony-claudeSymphony'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.