Claude Code, Codex CLI, Gemini CLI는 모두 비대화형 실행 방식을 제공한다. 각 CLI에 역할과 입력을 전달하고 결과를 수집할 수 있어, 작업 단계를 분리해 실행하는 오케스트레이터를 구현했다.

멀티 LLM 팀 오케스트레이터 구조

작업을 역할별로 분리하고, 각 에이전트는 독립된 git worktree에서 코드 초안과 리뷰 제안을 만든다.

구조

가운데에 오케스트레이터가 있고, 작업을 받으면 역할 단위로 분리해 각 에이전트에 할당한다. 역할은 YAML로 정의했다.

- role: architect
  cli: claude
  prompt: "설계 문서만 작성한다. 코드는 쓰지 않는다."
- role: implementer
  cli: codex          # codex 우선, 종료되면 gemini로 폴백
  fallback: [gemini, claude]
- role: reviewer
  cli: claude
  prompt: "리뷰하고 approve/reject를 JSON으로만 답한다."
- role: tester
  cli: claude
  prompt: "pytest 단위 테스트를 작성한다."

architect의 설계 결과를 implementer가 입력으로 사용하고, 이후 reviewer와 tester가 순서대로 실행되도록 의존성을 정의했다.

CLI마다 호출 방식이 달라 어댑터 계층을 두었다. Claude는 claude --bare, Codex는 codex exec --json, Gemini는 gemini -p ... --yolo로 호출한다. 공통 인터페이스를 통해 역할별 실행 도구와 폴백 순서를 설정할 수 있게 했다.

워크트리로 격리한 게 핵심이었다

초기에는 여러 에이전트가 같은 디렉터리에서 작업해 변경 파일과 커밋이 충돌했다. 에이전트마다 별도의 git worktree와 브랜치를 할당해 작업 공간을 분리했다. 병합 충돌은 자동 처리하지 않고 직접 검토하며, 결과를 사용하지 않는 실험 브랜치만 정리했다.

상태는 SQLite 체크포인트로 저장했다. 중간에 끊겨도 resume <task-id>로 이어서 실행한다. 동시 접근 때문에 WAL 모드와 asyncio 락을 함께 적용해야 했다.

종료된 세션 복구: claude-squad 기여

이것과 비슷한 도구로 claude-squad라는 게 있다. tmux로 에이전트 여러 개를 띄워서 관리해 주는 도구라 나도 같이 쓰고 있었다. 문제는 WSL이 재부팅되거나 시스템이 중단되면 tmux 세션이 사라진다는 점이었다. 그러면 cs가 없는 세션에 attach하려다 실패했다.

종료된 세션을 복구하는 cs recover 명령을 구현해 PR을 제출했다. tmux 세션을 시작하지 않고 저장된 메타데이터를 읽어, worktree는 남아 있지만 tmux 세션이 없는 항목을 찾는다. 해당 항목만 새 tmux 세션으로 시작하고 Claude에는 --resume을 전달해 이전 대화를 이어받도록 했다.

Alive (2): ✓ session-1 [branch-a]
Dead (1):  ✗ dead-worktree [branch-b]
Recoverable (3): ↻ agent-1 [branch-c]
Recovering 3 instance(s)...
  ✓ Recovered agent-1

main.go에 115줄 정도 들어갔다. 워크트리가 있고 tmux 세션이 종료된 경우에만 복구하도록 조건을 제한했다.

적용 결과와 한계

구현 과정은 흥미로웠지만 한계도 분명했다. 당시 Gemini CLI의 일부 오류는 폴백 경로로 우회해야 했고 Codex CLI의 동작도 자주 변경됐다. 세 도구를 하나의 인터페이스로 감싸더라도 각 CLI의 변경이 전체 오케스트레이터 안정성에 영향을 줬다.

그래도 역할을 분리해 할당하니 한 모델에 모든 일을 맡길 때보다 결과가 안정적이었다. 리뷰어를 따로 두고 approve/reject를 강제하니까 구현이 대충 넘어가는 경우가 줄었다. 이 워크플로를 피카츄 배구 RL 프로젝트에서 코드 초안·리뷰·테스트 보조로 써봤는데, 사람 쪽에서 설계와 검증을 어디까지 유지해야 하는지도 같이 보였다.


참고