주말 #2: 3-Way LLM 오케스트레이션(Orchestration) 스캐폴딩(Scaffolding)
요약
Claude Code, OpenAI Codex, 로컬 LLM을 병렬로 활용하기 위한 3-way 오케스트레이션 스캐폴딩 구축 사례를 소개합니다. Git worktrees와 Python 디스패처를 활용하여 여러 에이전트가 동일한 리포지토리에서 충돌 없이 작업할 수 있는 환경을 구현했습니다.
핵심 포인트
- 로컬 모델(Qwen)을 에이전트 하네스(OpenCode)와 결합하여 에이전틱 성능 강화
- Git worktrees를 이용해 다중 에이전트의 병렬 작업 및 충돌 방지
- Python 기반의 경량 디스패처를 통한 작업 큐 및 워크플로우 관리
- 자동 병합을 배제하여 에이전트 출력물에 대한 인간의 검토 프로세스 유지
지난번 저는 농담 반 진담 반으로 다음과 같은 말을 남기며 마무리했습니다. "토큰 사용량 제한에 걸리더라도 나의 바이브 코딩(vibe coding) 모멘텀을 유지하기 위해 ChatGPT, Claude, 그리고 나의 로컬 LLM 간의 3-way 오케스트레이션(orchestration)을 구축하겠다." 여러분 중 한 분은 오전 9시 50분에 Claude Code 세션이 새로고침되기를 간절히 기다리고 있다는 댓글을 남겨주셨습니다. 같은 마음이지만, 이번 주말은 다릅니다. 그래서 이번에는 실제로 그것을 구축했습니다.
시작점이 된 질문
저는 M1 Max, 64GB 통합 메모리 환경에서 Ollama를 통해 Qwen 2.5-Coder 32B를 로컬에서 실행하고 있습니다. 하드웨어는 고정되어 있으며, 조만간 업그레이드할 계획도 없습니다. 질문은 직설적이었습니다. 이 로컬 설정이 내가 한계에 부딪혔을 때 실제로 Claude Code를 대신할 수 있을까, 아니면 내가 스스로를 속이고 있는 것일까?
짧은 답변을 드리자면: 있는 그대로의 상태로는 불가능하지만, 예상보다 훨씬 근접했습니다. Qwen2.5-Coder 32B의 벤치마크는 GPT-4o 영역 근처에 위치합니다. 자동 완성(autocomplete)에는 적합하지만, Claude Code가 잘 수행하는 다단계(multi-step), 도구 호출(tool-calling) 에이전트 작업(agentic work)에는 취약합니다. 진짜 해결책은 단순히 더 큰 로컬 모델을 사용하는 것이 아니라, 단순한 Ollama 채팅 대신 더 나은 로컬 모델을 적절한 에이전트 하네스(agent harness)와 결합하는 것이었습니다.
로컬 측면에서 변경된 점
Qwen3-Coder-Next로 교체했습니다. 이는 MoE(Mixture of Experts) 모델로, 총 파라미터는 80B이지만 활성 파라미터는 3B뿐이어서 64GB 통합 메모리에 실제로 들어갑니다 (타이트합니다: Q4 기준 약 49GB를 사용하므로, 메모리를 많이 사용하는 다른 프로그램은 동시에 실행할 수 없습니다). 벤치마크 결과는 Sonnet-4.5급 코딩 성능에 근접하며, 이는 "로컬"이 장난감이 아닌 진정한 보조석(second seat)처럼 느껴진 첫 번째 사례입니다.
모델만큼이나 중요한 것이 하네스(Harness)입니다. 단순히 ollama run을 실행하는 것은 에이전틱 루프(agentic loop)가 아닙니다. 대신 저는 그 앞에 OpenCode를 배치했습니다. 이를 통해 로컬 모델을 리포지토리(repo)를 읽고, 계획을 세우며, 도구 호출(tool calls)을 통해 여러 파일을 편집할 수 있는 무언가로 변모시켰습니다. 이는 Claude Code나 Codex CLI가 이미 저에게 제공하고 있는 워크플로우와 동일한 형태입니다.
실제 오케스트레이션(orchestration) 구축하기
이 부분이 제가 실제로 주말 내내 매달린 작업입니다: agent-orchestra는 작고 투명한 코디네이터(coordinator)로, 제가 Claude Code, OpenAI의 Codex CLI, 또는 로컬 OpenCode+Qwen 에이전트에게 작업을 넘겨주고, 이 세 가지를 동일한 리포지토리에 대해 서로 충돌 없이 병렬로 실행할 수 있게 해줍니다.
의도적으로 지루하게 설계된 메커니즘:
- Git worktrees — 모든 작업은 각자의 브랜치(branch)와 작업 디렉토리(working directory)를 가집니다. 따라서 세 명의 에이전트가 충돌 없이 동시에 동일한 리포지토리를 편집할 수 있습니다.
- 표준 라이브러리(stdlib) 전용 Python 디스패처 (orchestrate.py) — 의존성 없이 약 250줄로 구성되었으며, add/run/status/review/merge/discard를 서브커맨드(subcommand)로 제공합니다. 작업을 큐(queue)에 넣고 자리를 비웠다 돌아오면 디프(diff)가 완성되어 있습니다.
- 어디에서도 자동 병합(auto-merge)을 허용하지 않음. 모든 에이전트의 출력물은 각자의 브랜치에 저장되고, 깔끔한 디프를 위해 자동으로 커밋(commit)되며, 제가 명시적으로 병합하기 전까지
status: review상태로 머뭅니다. 모든 에이전트를 빠르지만 일관성 없는 주니어 개발자처럼 취급하세요. 실제로 그렇기 때문입니다. - 에이전트별 동시성 제한 (concurrency caps) — 로컬 모델은 동시 작업이 1개로 제한됩니다 (현실적으로 한 번에 하나의 작업만 메모리에 들어갈 수 있음). 반면 Claude와 Codex는 단순한 네트워크 호출이므로 더 많은 여유 공간(headroom)을 가집니다.
이 모든 것을 실제 작업에 신뢰하기 전에, 저는 가짜 claude/codex/opencode 대역(stand-ins)을 작성하고, 버려도 상관없는 git 저장소를 대상으로 '추가(add) → 실행(run) → 검토(review) → 병합(merge)'의 전체 라이프사이클을 실행해 보았습니다. 이는 해볼 만한 가치가 있었습니다. 실제 버그 하나를 잡아냈기 때문입니다(대상 저장소 내부에 중첩된 worktree가 추적되지 않는 노이즈로 git status를 어지럽히는 문제). 이로 인해 최종 설계가 재구성되었습니다. 이제 agent-orchestra는 가리키고 있는 프로젝트 외부에서 독립적인 도구로 존재하며, cd로 이동한 어떤 저장소에 대해서든 셸 별칭(shell alias)을 통해 호출됩니다. 깔끔한 분리, 노이즈 없음, 그리고 이제는 실제 GitHub 저장소로 변환하기 위한 자체 init-repo.sh를 갖추게 되었습니다 (gh repo create ... --push 한 번의 명령으로 가능).
결과: 헤드리스(headless) 방식으로 호출된 Claude Code가 자체 git 워크트리(worktree) 내에서 파일과 테스트를 작성하고, 자신의 브랜치에 커밋한 뒤, 제가 diff를 검토하기를 기다리며 대기했습니다. 이는 제가 설계한 형태 그대로였습니다. 이론에 머물지 않고 실제 하드웨어에서 실제 버그를 찾아내고 수정하며, 루프(loop)가 엔드 투 엔드(end-to-end)로 작동한 것입니다.
현재 이를 구동하는 두 가지 방법:
- Claude Squad: 제가 진행 과정을 지켜보거나 조종하고 싶은 모든 작업 — 일상적인 주행(day-to-day driving).
- orchestrate.py: 실행 후 방치하는(fire-and-forget) 배치 작업 — 자기 전에 3개의 에이전트에 걸쳐 5개의 작은 작업을 큐(queue)에 쌓아두고, 아침에 diff를 검토합니다.
한 가지 솔직한 공백: 아직 로컬 모델(local-model) 경로를 실제로 검증하지는 못했습니다. 첫 번째 시도에서 "local에서 시작 중...(starting on local...)" 단계에 너무 오래 머물러서, 결국 포기하고 해당 작업을 Claude로 라우팅(routing)했습니다. Qwen3-Coder-Next의 콜드 스타트(cold start), 즉 토큰을 생성하기 전 49GB를 통합 메모리(unified memory)에 로드하는 과정은 단순한 반올림 오차 수준이 아니라 실제로 꽤 긴 기다림을 필요로 합니다. 이론적으로는 예열(warm)된 후에는 잘 작동하겠지만, 아직 그 과정을 끝까지 지켜보지는 못했습니다. 다음 세션의 과제입니다.
다음 단계
이제 세 명의 에이전트가 제가 컨텍스트 스위칭(context switch)을 일일이 관리하지 않고도 실제로 작업을 공유할 수 있게 되었으므로, 다음 갈증은 루프를 더 폐쇄적으로 만드는 것입니다. 즉, 제가 수동으로 담당자를 지정하는 대신 오케스트레이터(orchestrator)가 난이도에 따라 스스로 작업을 라우팅할 수 있을까요? 로컬 모델 경로에 대한 실제 콜드 스타트 타이밍 테스트는 여전히 숙제로 남아 있습니다. 그리고 지난번에 언급했던 상시 가동 아키텍처 위생(architecture-hygiene) 대시보드도 제대로 살펴봐야 합니다.
여러분의 로컬 모델 한계점(breaking point)은 어디인가요? 로컬 LLM으로 오프로딩(offloading)하려고 시도했지만, 도저히 싸워볼 가치가 없다고 느꼈던 작업은 무엇인가요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기