에이전트 위임 아키텍처 — task() 하나와 GJC Coordinator MCP의 차이
Found mermaid block: 1764 chars REPLACED
15개 프로필을 설계해놓고, 실제 작업 위임에서 막혔다. 에이전트 프로필 15개를 만든 설계를 한 뒤였다. explorer, implementer, reviewer, content-manager — 각자 할 일은 명확했다. 문제는 하나, 누구한테 어떻게 일을 넘기느냐였다.
처음에는 task() 하나로 다 했다. task()는 subagent를 호출하는 가장 단순한 방식이다. parent 세션 안에서 바로 실행되니까 컨텍스트 공유가 공짜고 startup도 0에 가깝다. 근데 그 단순함이 양날의 검이다. 모든 subagent가 같은 세션, 같은 모델, 같은 repo에서 돌았다. 단순했다. 그래서 위험했다. subagent 하나가 json 로드하다가 crash 나면 parent까지 죽었다. 여기저기서 파일을 수정하니 side effect가 눈덩이처럼 쌓였다. 병렬 실행은 꿈도 못 꿨다.
결국 두 갈래로 찢어졌다. “의견은 같은 방에서, 실행은 각자 방에서” 라는 패턴이 자연스럽게 나왔다.
이 글에서 다루는 것
task()의 한계 — isolation 부재, crash 전파, 병렬 실행 불가 GJC Coordinator MCP의 등장 — worktree 격리, tmux 병렬, structured workflow Two-tier 위임 라우팅 — 같은 모델이면 task(), 격리/병렬이면 GJC “비용 모델이 isolation을 결정한다”는 설계 원칙 실제 실패 모드와 대응 설계
왜 위임 방법을 두 개로 나눴는가
단일 위임 방식의 유혹은 알겠다. “그냥 task() 하나로 다 하면 안 되나?” 안 된다. 이유는 셋이다.
첫째, isolation. task()는 parent 세션의 연장선이다. 같은 컨텍스트, 같은 파일시스템, 같은 프로세스 공간. subagent가 파일을 쓰면 parent repo가 즉시 오염된다. implementer가 버그 있는 코드를 커밋하면 reviewer가 보기도 전에 문제가 생긴다. GJC는 git worktree로 브랜치를 분기한다. 격리된 공간에서 작업하고, 통과해야만 머지된다.
둘째, crash 전파. task()에선 모든 subagent가 parent와 운명을 공유한다. 하나가 죽으면 다 죽는다. Python nested JSON scope 버그처럼, 예측 못 한 런타임 에러가 subagent 하나를 죽이면 parent 전체가 끝난다. GJC는 tmux로 각 subagent를 독립 프로세스로 띄운다. 하나 죽어도 나머지는 산다.
셋째, 병렬 실행. task()는 sequential이다. 하나 끝나야 다음. GJC의 gjc_delegate_team은 tmux로 여러 작업을 동시에 돌린다. implementer가 A 모듈을 만드는 동안 tester가 B 모듈을 검증할 수 있다.
Two-Tier Delegation task() — 같은 세션, 같은 모델, isolation 없음. review/editor/archiver 용. gjc_delegate_* — worktree 격리, tmux 병렬, structured workflow. implement/refactor/파일 쓰기 용. rule of thumb: 파일을 쓰면 GJC. 읽기만 하면 task().
빌드 경로 — 세 단계의 진화
Slice 1: OMO subagent (지금은 폐기됨)
처음에는 OMO subagent profile 시스템을 썼다. delegate.ts + agents/*.md로 프로필을 정의하고, delegate()로 위임했다. explorer는 kimi-k2.7-code, reviewer는 deepseek-v4-pro 같은 식으로 프로필마다 모델을 지정할 수 있었다.
이 방식의 문제는 isolation이 전혀 없었다는 점이다. 모델은 다르게 쓸 수 있었지만, 결국 같은 repo에서 같은 파일을 건드렸다. implementer가 리팩토링하는 동안 reviewer가 같은 파일을 읽어서 race condition이 발생했다. 버전 관리가 안 됐다.
2026-06-23, 이걸 갈아엎었다.
Slice 2: GJC Coordinator MCP로 교체
GJC의 핵심 발상은 단순하다. “각자 방에서 작업하게 하자.” implementer는 자기 브랜치에서 코드를 쓰고, tester는 동시에 다른 브랜치에서 검증한다. 이걸 가능하게 하는 게 worktree 격리와 tmux 병렬 실행이다. worktree는 git의 ‘각자 방’ 같은 거다 — 같은 레포지토리에 살지만 서로 폴더를 침범하지 않는다. tmux는 여러 터미널 세션을 각자 창에서 관리하는 도구다. 이 조합에 structured workflow까지 붙어서 나온 게 GJC Coordinator MCP다.
GJC가 제공하는 것:
gjc_delegate_execute — 단일 작업을 worktree에 격리해서 실행. goal + acceptance criteria를 지정하면, 통과할 때까지 반복. gjc_delegate_team — 병렬 tmux worker orchestration. A, B, C 세 작업을 동시에 다른 worktree에서 실행. gjc_delegate_plan — deep-interview + ralplan 워크플로. 복잡한 작업은 들어가기 전에 먼저 계획을 세운다.
GJC는 OPENCODE_API_KEY로 opencode-go 모델 풀(flash/pro/code/max)에 접근한다. 즉, 모델 라우팅과 execution isolation이 하나의 인터페이스로 통합된 셈이다.
Slice 3: Two-tier 라우팅 — pattern이 잡혔다
OMO를 버리고 GJC를 붙이고 나서, 위임을 결정하는 기준이 자연스럽게 정리됐다.
“파일을 쓰느냐, 읽기만 하느냐.” 이게 전부다.
작업 유형 위임 방식 근거
review (코드 리뷰) task() 읽기 전용. fast feedback이 중요
editor (문서 수정) task() 작은 파일 단위. side effect 적음
archiver (정리) task() P2-hippocampus만 건드림. 안전
explorer (분석) task() 읽기 전용. 코드베이스 탐색
implementer (구현) gjc_delegate_execute 파일 생성/수정. worktree 필수
refactoring gjc_delegate_execute 대규모 파일 변경. 격리 + acceptance check
병렬 작업 (A+B+C) gjc_delegate_team tmux 병렬. 각자 worktree
복잡한 계획 필요 gjc_delegate_plan deep-interview로 분석 먼저
이 테이블을 외울 필요는 없다. 규칙은 하나다: 파일을 쓰는 작업은 무조건 GJC로. 읽기만 하는 작업은 task()가 더 빠르다. isolation이 필요 없는 곳에 worktree overhead를 지불할 이유가 없다.
아키텍처 다이어그램
“비용 모델이 isolation을 결정한다”
이 설계에서 가장 중요한 인사이트는 하나다. isolation은 그냥 좋은 게 아니라, 비용에 따라 적용하는 것이다.
task()는 공짜다. 같은 세션이니까 컨텍스트 전환 비용이 없다. parent 모델을 그대로 상속받는다. startup latency가 0이다. reviewer에게 “이 코드 리뷰해줘” 하는 데 worktree를 만들 이유가 전혀 없다. isolation은 보험이다. 보험은 필요할 때만 드는 거다.
GJC는 비용이 있다. worktree를 만들고, tmux 세션을 띄우고, 별도 모델을 호출한다. 근데 이 비용은 사고가 났을 때의 복구 비용보다 훨씬 싸다. implementer가 잘못된 코드를 parent repo에 직접 써버리면, 그걸 되돌리는 데 드는 인지 비용이 worktree 생성 비용의 10배는 된다.
그래서 규칙이 생겼다. “파일을 쓰면 GJC, 읽기만 하면 task().” 이 규칙은 기술적 필요에서 나온 게 아니다. 경제적 판단이다. 실패 비용 vs 격리 비용의 균형점을 찾은 거다.
GJC의 structured workflow — 단순한 실행을 넘어서
task()는 “이거 해줘” → “했다” 로 끝난다. GJC는 다르다. AI agent에게 가장 부족한 걸 보완해준다: 목표 지향성. LLM은 원래 길을 잃기 쉽다. 한 가지를 수정하다가 열 가지를 건드리고, 원래 뭘 하려고 했는지 잊어버린다. structured workflow는 이걸 구조로 막는다.
deep-interview: 작업을 던지기 전에, 계획을 세운다. 어떤 파일을 건드릴지, 의존성이 뭔지, 예상되는 사이드 이펙트가 뭔지 먼저 분석한다. 쉽게 말해 “일단 해보고 안 되면 돌아오자”가 아니라 “어디로 가는지 알고 가자”다.
ralplan: deep-interview에서 나온 분석을 실행 가능한 단계로 쪼갠다. 각 단계마다 acceptance criteria가 있다. “이 단계가 완료됐다고 판단하는 기준이 뭐지?”를 미리 정해둔다.
ultragoal: 가장 큰 그림. “이 리팩토링의 최종 목표는 뭐지?” 수준의 acceptance criteria. 모든 하위 단계가 이걸 향해 수렴한다.
이 structured workflow는 AI agent에게 가장 부족한 걸 보완해준다: 목표 지향성. LLM은 원래 길을 잃기 쉽다. 한 가지를 수정하다가 열 가지를 건드리고, 원래 뭘 하려고 했는지 잊어버린다. deep-interview → ralplan → ultragoal 파이프라인은 이걸 구조로 막는다.
실패 모드와 대응 설계
실패 모드 어디서 생겼나 대응
subagent crash → parent 죽음 task() 파일 쓰는 작업은 task()로 안 보냄. GJC는 tmux로 프로세스 격리
repo side effect 누적 OMO, task() GJC worktree. 통과 못 하면 머지 안 됨
병렬 실행 불가 task() gjc_delegate_team. tmux로 동시 실행
agent가 목표를 잃어버림 모든 방식 structured workflow (deep-interview → ralplan → ultragoal)
느린 startup (MCP 부트스트랩 등) GJC 읽기 전용은 task()로. GJC 세션은 재사용. warm cache 유지
프로필 모델 불일치 OMO 같은 모델 = task(). 다른 모델 필요 = GJC. GJC는 OPENCODE_API_KEY로 모델 풀 접근
15개 프로필과의 관계 — Ep.14에서 여기까지
Ep.14에서 15개 에이전트 프로필을 설계한 이야기를 했다. explorer → implementer → reviewer 파이프라인, orchestrator의 분배 로직, content-manager의 자율 발행. 그때는 “누가 무엇을 할지”를 설계한 거였다.
이번 Ep.19는 그 연장선이다. “어떻게 위임할지”를 설계한 거다. 프로필 설계가 role-based access control이라면, 위임 아키텍처는 execution engine이다.
둘이 만나면 이렇게 작동한다:
orchestrator가 kanban task를 분류한다: “이건 읽기 전용 분석이니까 explorer 프로필을 task()로” “이건 새 모듈 구현이니까 implementer 프로필을 gjc_delegate_execute로” “A, B, C 모듈을 동시에 만들어야 하니까 implementer × 3을 gjc_delegate_team으로”
프로필은 역할을 정의하고, 위임 아키텍처는 실행 환경을 정의한다. 이 두 레이어가 분리돼 있다는 게 중요하다. 프로필을 바꾸지 않고도 위임 방식을 바꿀 수 있고, 반대로 위임 방식을 건드리지 않고 프로필만 추가할 수 있다.
인간 팀도 이렇게 일한다
이 아키텍처를 정리하고 나서 깨달았다. 이건 그냥 인간 팀의 작업 방식과 똑같다.
코드 리뷰는 같은 방에서 한다. PR 페이지 하나 띄워놓고 “여기 이렇게 하면 어때?”라고 말한다. 별도 브랜치를 만들 필요가 없다. 의견 교환은 가볍고 빨라야 한다. 그래서 task().
구현은 각자 방에서 한다. feature 브랜치를 따고, 자기 컴퓨터에서 빌드하고, 테스트 통과하면 PR을 올린다. 다른 사람 컴퓨터를 오염시키지 않는다. 그래서 GJC.
“의견은 같은 방에서, 실행은 각자 방에서.” 이 원칙 하나로 위임 아키텍처가 정리됐다. 복잡한 규칙은 필요 없었다. “파일을 쓰면 격리” — 이 한 문장이면 충분했다.
남은 과제
아직 다 해결된 건 아니다. 몇 가지 계속 고민 중인 지점:
GJC worktree cleanup — worktree가 늘어나면 디스크를 잡아먹는다. 자동 정리 정책이 필요하다. 실패한 worktree의 재사용 — implementer가 실패한 worktree를 다시 쓸지, 새로 만들지 결정하는 로직이 아직 heuristic이다. task()의 isolation 강화 — 지금은 완전히 같은 세션이지만, 읽기 전용 task()라도 최소한의 격리(별도 컨텍스트 윈도우)가 필요할 수 있다. 비용 투명성 — GJC 호출이 몇 번 일어났는지, worktree 생성에 얼마나 걸렸는지 모니터링이 부족하다.
하지만 지금 구조만으로도 충분히 안정적이다. OMO 시절에 비하면 subagent crash로 parent가 죽는 일은 0건이다. worktree 덕분에 repo 오염도 없다. 병렬 실행으로 하루 작업량이 눈에 띄게 늘었다. 이 정도면 production에서 쓸 만한 수준이다.
이 글은 Season 1: Taste Engineering의 Ep.19다. 이전 글: 에이전트 프로필 15개를 만든 설계 (Ep.14), Taste는 감각이 아니라 정책이다 (Ep.15). Drewgent 빌드 로그의 다른 에피소드는 Systems 태그에서 볼 수 있다.
읽어줘서 고맙다.