본문 바로가기

AI Development

OpenAI Symphony 완전 가이드 — Linear 이슈 트래커를 에이전트 컨트롤 플레인으로 만드는 법

반응형

이슈 하나당 에이전트 하나. 사람은 결과만 리뷰. OpenAI 내부팀 PR 500% 증가. 오픈소스로 공개됐습니다.

[핵심 요약]
→ 출시: 2026년 4월 27일 (OpenAI 오픈소스)
→ 라이센스: Apache 2.0
→ GitHub: github.com/openai/symphony
→ 정체: 이슈 트래커(Linear) → 코딩 에이전트 자동 오케스트레이터
→ 핵심: 이슈 하나 = 에이전트 하나 = 독립 워크스페이스 = PR 하나
→ 효과: OpenAI 내부 팀 PR 3주 만에 500% 증가
→ 구현: Elixir 레퍼런스 구현 포함 (다른 언어 포팅 가능)
→ 주의: Claude Code + GitHub Issues 포트 버전도 이미 등장
→ 한계: 복잡한 판단이 필요한 작업 → 대화형 세션이 여전히 적합

 


왜 Symphony가 나왔나

기존 코딩 에이전트 사용 방식:
→ 엔지니어가 Codex 세션 열기
→ 태스크 직접 지시
→ 결과 확인 후 다음 태스크 지시
→ 동시에 3~5개 세션이 한계 (컨텍스트 전환 과부하)
→ 에이전트 속도가 빨라져도 사람이 병목

OpenAI가 발견한 문제:
"에이전트를 더 빠르게 만들었더니
이번엔 사람이 세션을 관리하는 게 병목이었다"

Symphony의 해결책:
→ 세션 관리 → 태스크 관리로 전환
→ Linear 이슈 트래커 = 에이전트 컨트롤 플레인
→ 이슈 열리면 → 에이전트 자동 배정
→ 에이전트가 크래시 → 자동 재시작
→ PR 완료 → 사람 리뷰 요청
→ 사람 역할: 태스크 정의 + 결과 리뷰만
[500% PR 증가의 의미]
→ 3주 만에 일부 팀 PR 5배 증가
→ 엔지니어가 더 열심히 일한 게 아님
→ 에이전트가 24시간 이슈 큐를 소화
→ 사람은 리뷰 + 더 복잡한 문제에 집중

주의: "PR 수 = 품질" 아님
→ 검증 부담도 함께 증가
→ 자동화 테스트/가드레일 필수

Symphony 동작 원리

[Symphony 아키텍처]

Linear 이슈 트래커
    ↓ (주기적 폴링)
Symphony 오케스트레이터
    ↓ (이슈 상태 확인)
    ├─ Todo 이슈 발견
    │   ↓
    │   독립 워크스페이스 생성
    │   ↓
    │   코딩 에이전트 실행 (Codex/Claude Code)
    │   ↓
    │   CI 모니터링 + 충돌 해결
    │   ↓
    │   PR 생성 → Linear 상태 변경 (Human Review)
    │
    ├─ 에이전트 크래시 → 자동 재시작
    └─ 새 이슈 추가 → 자동 픽업

사람:
→ 이슈 작성 (태스크 정의)
→ PR 리뷰 (결과 검증)
→ 머지 또는 피드백
[이슈 상태 머신]
Todo → In Progress → Human Review → Done
  ↑                      |
  └──── 실패 시 재시작 ───┘

Symphony가 관리:
→ Todo 감지 → In Progress 전환
→ PR 생성 → Human Review 전환
→ 크래시 → 다시 In Progress
→ 머지 → Done

실전 1 — 설치 및 환경 설정

# 필요 조건
# - Elixir 1.17+
# - Linear API 키
# - Codex API 키 (또는 다른 에이전트)
# - GitHub 연동

# 레포지토리 클론
git clone https://github.com/openai/symphony
cd symphony

# Elixir 설치 (macOS)
brew install elixir

# 의존성 설치
cd elixir
mix deps.get

# 환경 변수 설정
export LINEAR_API_KEY="lin_api_..."      # Linear API 키
export OPENAI_API_KEY="sk-..."           # Codex/GPT API 키
export GITHUB_TOKEN="ghp_..."            # GitHub 토큰
export LINEAR_TEAM_ID="your-team-id"    # Linear 팀 ID
export LINEAR_WORKFLOW_STATE_TODO="..."  # Todo 상태 ID
export LINEAR_WORKFLOW_STATE_IN_PROGRESS="..."
export LINEAR_WORKFLOW_STATE_IN_REVIEW="..."

# Symphony 실행
mix run --no-halt
[Linear 설정 (필수)]
1. Linear 워크스페이스 → Settings → API → API Keys 생성
2. Linear 팀 → Settings → Workflow 확인
   → 최소 상태: Todo / In Progress / In Review / Done
3. Linear Settings → Labels → "symphony" 레이블 생성
   → 이 레이블이 붙은 이슈만 Symphony가 픽업
4. GitHub Integration → Linear에서 GitHub 연동

실전 2 — WORKFLOW.md 작성

Symphony의 핵심은 레포지토리 루트의 WORKFLOW.md입니다. 에이전트가 어떻게 일할지 정의합니다.

<!-- WORKFLOW.md (프로젝트 루트) -->

# Symphony Workflow

## 에이전트 행동 지침

### 기본 원칙
- 하나의 이슈 = 하나의 PR
- 변경은 최소화 (이슈 범위만)
- 모든 변경은 테스트 포함
- 커밋 메시지: Conventional Commits 형식

### 코딩 컨벤션
- 언어: TypeScript (strict mode)
- 포맷터: Prettier (자동 실행)
- 린터: ESLint (경고 = 실패)
- 테스트: Jest (커버리지 80%+)

### 작업 순서
1. 이슈 내용 완전히 이해
2. 관련 파일 파악
3. 테스트 먼저 작성 (TDD)
4. 구현
5. 기존 테스트 모두 통과 확인
6. PR 생성 (이슈 번호 포함)

### PR 제목 형식
feat: [이슈 제목] (closes #이슈번호)

### 자동화 불가 판단 기준
다음 상황에서는 PR 대신 코멘트로 사람에게 위임:
- 아키텍처 결정이 필요한 경우
- 이슈 설명이 모호한 경우
- 외부 API 키/비밀이 필요한 경우
- 데이터베이스 마이그레이션이 복잡한 경우
[좋은 WORKFLOW.md 작성 원칙]
→ 구체적으로: 에이전트가 추측하지 않도록
→ 실패 조건 명시: 언제 사람에게 넘길지
→ 테스트 기준: 자동 검증 가능하게
→ 범위 제한: "이슈 범위만" 명시 (무한 확장 방지)
→ 코딩 컨벤션: 린터/포맷터 자동화로 일관성 확보

실전 3 — Linear 이슈 잘 쓰는 법

Symphony는 이슈 내용을 그대로 에이전트에게 전달합니다. 이슈 품질 = 에이전트 품질입니다.

<!-- ❌ 나쁜 이슈 -->
제목: 로그인 버그 수정
내용: 로그인이 안 됨

<!-- ✅ 좋은 이슈 -->
제목: feat: JWT 토큰 만료 후 자동 갱신 구현

## 문제
만료된 JWT 토큰으로 API 호출 시 401 반환되지만
자동 갱신 없이 사용자가 로그아웃됨

## 기대 동작
- 액세스 토큰 만료 시 리프레시 토큰으로 자동 갱신
- 갱신 실패 시 /login 리다이렉트

## 영향 파일
- src/api/client.ts (인터셉터)
- src/auth/token.ts (토큰 관리)
- src/hooks/useAuth.ts

## 완료 기준
- [ ] 401 응답 시 자동 갱신 인터셉터 동작
- [ ] 갱신 실패 시 로그아웃 처리
- [ ] 단위 테스트 추가 (토큰 갱신 성공/실패)
- [ ] 기존 테스트 전부 통과

## 참고
관련 코드: src/api/client.ts:45-67
[Symphony에 적합한 이슈 특성]
✅ 적합:
→ 명확한 완료 기준이 있는 작업
→ 코드베이스 이해로 해결 가능한 버그
→ 패턴 반복 작업 (타입 힌트 추가, 테스트 작성)
→ 문서화, 리팩토링
→ 잘 정의된 신규 기능

❌ 부적합:
→ "더 좋게 만들어줘"
→ 아키텍처 결정이 필요한 작업
→ 외부 서비스 통합 (API 키 필요)
→ 복잡한 DB 마이그레이션
→ UI/UX 주관적 판단이 필요한 작업

실전 4 — Claude Code + GitHub Issues 포트 (Claude Code 사용자용)

OpenAI Symphony가 Codex + Linear 조합이라면, 커뮤니티에서 Claude Code + GitHub Issues 버전을 만들었습니다.

# .github/workflows/symphony-claude.yml
name: Symphony - Claude Code Orchestrator

on:
  issues:
    types: [labeled]  # "symphony" 레이블 추가 시 트리거

jobs:
  run-agent:
    if: contains(github.event.label.name, 'symphony')
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Install Claude Code
        run: npm install -g @anthropic-ai/claude-code

      - name: Run Claude Code Agent
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # 이슈 내용 가져오기
          ISSUE_BODY="${{ github.event.issue.body }}"
          ISSUE_TITLE="${{ github.event.issue.title }}"
          ISSUE_NUMBER="${{ github.event.issue.number }}"

          # Claude Code로 태스크 실행
          claude --no-interactive -p "
          다음 GitHub 이슈를 해결해주세요:

          제목: $ISSUE_TITLE
          내용: $ISSUE_BODY

          완료 후:
          1. 변경사항 커밋 (feat/fix: $ISSUE_TITLE)
          2. PR 생성 (closes #$ISSUE_NUMBER)
          3. PR 링크를 이슈 코멘트로 남기기

          WORKFLOW.md의 지침을 반드시 따르세요.
          "

      - name: Comment on failure
        if: failure()
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: ${{ github.event.issue.number }},
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: '❌ Symphony 에이전트가 이 이슈를 자동 처리할 수 없습니다. 수동 처리가 필요합니다.'
            })
# Python으로 구현한 간단한 Symphony 스타일 오케스트레이터
import anthropic
import subprocess
import time
from github import Github  # pip install PyGithub

class SimpleSymphony:
    """Symphony 스타일 에이전트 오케스트레이터"""

    def __init__(
        self,
        github_token: str,
        repo_name: str,    # "owner/repo"
        label: str = "symphony",
        poll_interval: int = 60   # 초
    ):
        self.gh         = Github(github_token)
        self.repo       = self.gh.get_repo(repo_name)
        self.label      = label
        self.poll_interval = poll_interval
        self.active_issues = set()

    def run(self):
        """메인 오케스트레이션 루프"""
        print(f"Symphony 시작: {self.repo.full_name}")

        while True:
            try:
                self._process_pending_issues()
            except Exception as e:
                print(f"오케스트레이터 오류: {e}")

            time.sleep(self.poll_interval)

    def _process_pending_issues(self):
        """대기 중인 이슈 처리"""
        issues = self.repo.get_issues(
            state="open",
            labels=[self.label]
        )

        for issue in issues:
            if issue.number not in self.active_issues:
                print(f"새 이슈 발견: #{issue.number} {issue.title}")
                self._dispatch_agent(issue)

    def _dispatch_agent(self, issue):
        """이슈에 에이전트 배정"""
        self.active_issues.add(issue.number)

        # 이슈 코멘트로 시작 알림
        issue.create_comment("🤖 Symphony 에이전트가 작업을 시작합니다...")

        try:
            # 독립 워크스페이스 생성 (새 브랜치)
            branch_name = f"symphony/issue-{issue.number}"

            prompt = f"""
GitHub 이슈를 해결해주세요:

제목: {issue.title}
내용:
{issue.body}

작업 지침:
1. '{branch_name}' 브랜치에서 작업
2. WORKFLOW.md 지침 준수
3. 모든 테스트 통과 확인
4. PR 생성 (closes #{issue.number})
"""
            # Claude Code 실행
            result = subprocess.run(
                ["claude", "--no-interactive", "-p", prompt],
                capture_output=True,
                text=True,
                cwd="."
            )

            if result.returncode == 0:
                issue.create_comment("✅ PR 생성 완료. 리뷰를 요청합니다.")
            else:
                issue.create_comment(
                    f"❌ 자동 처리 실패. 수동 처리가 필요합니다.\n"
                    f"오류: {result.stderr[:500]}"
                )
                self.active_issues.discard(issue.number)

        except Exception as e:
            issue.create_comment(f"❌ 에러: {str(e)}")
            self.active_issues.discard(issue.number)

실전 5 — 코드베이스 Symphony 준비 체크리스트

Symphony가 효과적으로 작동하려면 코드베이스가 에이전트 친화적이어야 합니다.

[에이전트 친화적 코드베이스 체크리스트]

✅ 자동화 테스트
□ 단위 테스트 커버리지 70%+
□ 테스트 실행: 한 줄 명령 (npm test / pytest)
□ CI 파이프라인: PR 시 자동 실행
□ 실패 시 명확한 에러 메시지

✅ 코드 품질 자동화
□ 린터: 자동 실행 + 실패 시 PR 차단
□ 포맷터: 자동 적용 (Prettier/Black)
□ 타입 체커: TypeScript/mypy
□ pre-commit 훅 설정

✅ 명확한 구조
□ 파일/폴더 명명 규칙 문서화
□ 새 기능 추가 위치 명확 (어디에 뭘 놓는지)
□ 의존성 관리 명확 (package.json / requirements.txt)

✅ WORKFLOW.md
□ 코딩 컨벤션 명시
□ 완료 기준 (어떻게 하면 done인지)
□ 자동화 불가 판단 기준
□ PR 형식 규칙

✅ 이슈 템플릿 (.github/ISSUE_TEMPLATE/)
□ symphony용 이슈 템플릿 생성
□ 완료 기준 체크리스트 포함
□ 영향 파일 섹션 포함

Symphony vs 기존 방식 비교

               기존 대화형 에이전트    Symphony
작업 방식:      세션 단위              이슈 단위
사람 역할:      매 단계 지시           태스크 정의 + 결과 리뷰
병렬 처리:      3~5개 세션 한계        이슈 수만큼 병렬
에이전트 복구:  수동 재시작            자동 재시작
상태 추적:      세션 내부              이슈 트래커 (가시적)
적합한 작업:    복잡한 판단 필요       명확히 정의된 태스크
비용:           세션당                 이슈당 (토큰 많이 씀)
[Symphony 실전 사용 팁]
→ 이슈를 작게 쪼개라: 큰 이슈는 에이전트가 방황
→ WORKFLOW.md에 실패 기준 명시: 언제 멈출지 알아야 함
→ 자동 테스트 먼저: 테스트 없으면 에이전트 검증 불가
→ 비용 모니터링: 연속 실행은 토큰 많이 씀 (Dan McAteer: "주당 수십 이슈")
→ 처음엔 소규모로: 3~5개 이슈로 시작해서 패턴 확인 후 확장

마무리

✅ Symphony 도입해야 할 때
→ 코딩 에이전트를 이미 쓰고 있고 세션 관리가 병목일 때
→ 잘 정의된 반복 태스크가 많을 때 (버그 수정, 테스트 추가)
→ 자동화 테스트 커버리지가 충분한 코드베이스
→ Linear 사용 팀 (다른 트래커는 포팅 필요)
→ 24시간 에이전트 큐 소화가 필요한 팀

❌ 아직 이른 경우
→ 자동화 테스트가 부족한 코드베이스 (에이전트 검증 불가)
→ 이슈 품질이 낮은 팀 (모호한 이슈 = 모호한 PR)
→ 아키텍처 결정이 많은 초기 단계 프로젝트
→ Elixir 낯선 팀 (레퍼런스 구현 기반, 포팅 필요)
→ 토큰 비용 민감한 소규모 팀

관련 글:

https://cell-devlog.tistory.com/89

 

Claude Code Agent Teams 실전 가이드 — 병렬 에이전트 설정부터 실전 시나리오까지

2026년 2월, Claude Opus 4.6과 함께 Agent Teams가 출시됐어요.기존:Claude Code 1개 → 순서대로 처리API 레이어 → DB 레이어 → 테스트 → 문서화(순차 실행)Agent Teams:Claude Code 여러 개 → 동시에 처리Agent 1: A

cell-devlog.tistory.com

https://cell-devlog.tistory.com/25

 

AI 에이전트 오케스트레이션 패턴 3가지 — Pipeline, Supervisor, Swarm 실전 비교

여러 에이전트를 만들고 나면 이런 고민이 생겨요."에이전트들을 어떻게 연결하지? 누가 누구한테 일을 시키고, 결과는 어떻게 모으지?"이게 오케스트레이션 패턴 선택의 문제예요. 패턴을 잘못

cell-devlog.tistory.com

 

반응형