본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 06. 18. 09:22

한 대의 AI에게 모든 것을 맡기는 것을 그만두었다. 역할 분담 멀티 에이전트를 Claude Code 플러그인으로 배포하기

요약

Claude Code의 기능을 활용하여 역할을 분담하는 멀티 에이전트 시스템을 구축하고 이를 플러그인 형태로 배포하는 방법을 소개합니다. Agent Teams와 Workflow를 결합한 하이브리드 구조와 Critic 게이트를 통한 검증 메커니즘이 핵심입니다.

핵심 포인트

  • 역할별(Orchestrator, Planner, Worker, Critic) 멀티 에이전트 체제 구축
  • Agent Teams(대화형)와 Workflow(결정론적 배치)의 하이브리드 구성
  • Critic 게이트를 통한 코드 및 동작의 객관적 검증
  • Claude Code 플러그인 형태로 배포하여 재사용성 확보

먼저 작동하는 모습부터. 하나의 태스크를 여러 에이전트에게 분담시키면, 화면이 분할되어 각각 병렬로 동작한다.

태스크를 분담한 여러 에이전트가 화면에 나뉘어 동시에 동작함

TL;DR

역할을 「인지 기능」으로 정의한 멀티 에이전트 (Multi-agent) 개발 체제 (Orchestrator / Planner / Worker / Critic). 회사의 직책으로 치면 부장 / 과장 / 담당자 / 고문 에에 해당한다. - 전반부는 동적인 Agent Teams, 후반부는 결정론적인 Workflow로 돌리는 하이브리드 구성. 핵심은 Critic 게이트 (자기 보고를 신뢰하지 않고 실제 코드와 실제 동작으로 확인하는 검증 관문).

  • 이를 다른 프로젝트나 다른 머신에서도 사용할 수 있는 Claude Code 플러그인 형태로 배포할 수 있게 만들었다. 설계 → 구현 → 테스트 → public 배포 → 실제 태스크 완수까지 마쳤다.
  • 진행 과정에서 맞닥뜨린 비자명한 기술적 포인트 (플러그인 변수의 전개 범위, Workflow의 이름 해결, worktree 격리의 전제 조건 등...)도 후반부에 정리했다 (플러그인을 직접 만드는 사람들을 위한 심도 있는 내용).

이미 플러그인을 공개했으므로 꼭 이용해 보시기 바란다.

리포지토리: https://github.com/KoyanagiAyuha/team-framework

먼저 용어 (Claude Code 사전 지식)

이 기사는 Claude Code의 「복수 에이전트」 기능을 토대로 하고 있다. 먼저 최소한의 용어를 정리해 둔다.

용어한마디로 말하면
에이전트 (Agent) / 서브 에이전트 (Sub-agent)하나의 태스크를 담당하는 독립된 Claude 인스턴스
Agent Teams / teammate여러 Claude 세션을 대화시키면서 협조 동작하게 하는 메커니즘. 세운 1체를 teammate라고 한다. 대화적이며, 인간이 중간에 개입할 수 있다
spawnteammate (또는 subagent)를 기동하는 것
WorkflowJS 스크립트로 다수의 서브 에이전트를 결정론적으로 병렬 실행하는 메커니즘. 백그라운드에서 실행되며 중간 개입은 하지 않는다
skill/이름으로 호출할 수 있는 절차서 (프롬프트 + 절차의 패키지)
hook툴 실행 전후 등에 자동으로 실행되는 자체 스크립트 (SessionStart, PreToolUse 등)
plugin / marketplace배포 패키지 본체 / 해당 배포처 카탈로그
worktreegit의 작업 트리(working tree)를 별도 디렉토리로 나누는 기능. 병렬 작업의 격리에 사용

대략적으로 "Teams = 대화적으로 협조", "Workflow = 결정론적으로 배치(Batch)", "plugin = 배포의 단위"라고만 파악하면 읽어나갈 수 있다.

왜 멀티 에이전트 (Agent Teams)인가

한 대의 Claude에게 전부 시키는 것이 아니라, 여러 명으로 나누는 이점은 크게 3가지다.

컨텍스트를 나눌 수 있다: 1대가 조사, 구현, 검증을 모두 떠안으면 컨텍스트 (작업 문맥)가 비대해져 정밀도가 떨어진다. 역할별로 나누면 각자는 클린한 문맥에서 자신의 업무에만 집중할 수 있다. -
구현과 검증을 다른 사람으로 할 수 있다: 자신의 성과물에는 자신에게 관대해지기 마련이다. 구현 역할 (Worker)과 검증 역할 (Critic)을 나누면, 검증이 "이해관계 없이" 이루어져 놓치는 부분이 줄어든다. -
병렬로 빨라지며 / 역할별로 최적화할 수 있다: 독립된 태스크는 동시에 실행할 수 있다. 역할에 따라 모델 (무거운 분해는 상위 모델, 단순한 구현은 가벼운 모델)이나 지시 사항도 바꿀 수 있다.

반대로, 작은 단발성 태스크라면 1대로 충분하다. 나누는 비용에 상응하는 경우는 「규모가 크고, 병렬화가 가능하며, 독립적인 검증이 필요한 때」다.

배경: 무엇을 해결하고 싶었는가

Claude Code에서 여러 에이전트를 협조시키려고 하면, 처음에 두 가지 문제로 고민하게 된다.

역할을 어떻게 나누고 정의할 것인가: 「누가 무엇을 담당할지」를 나중에 증감하거나 유용하기 쉬운 형태로 어떻게 부여할 것인가. -
어떻게 재사용할 것인가: 프로젝트마다 .claude (Claude Code의 설정 일체)를 복사해서 들고 다니는 것은 한계가 있다. 한 번 설정하면 모든 프로젝트에서 사용할 수 있는 형태로 만들고 싶다.

그래서 두 가지 방침을 세웠다.

역할은 인지 기능으로 정의한다 ("생각하는 역할" / "움직이는 역할" / "의심하는 역할". 상세 내용은 다음 절).

  • 배포 가능한 플러그인으로 만든다.

역할 (회사의 직책에 비유하면)

역할은 "어떤 인지 기능(Cognitive Function)을 담당하는가"로 정의한다. 다만 이는 추상적이므로, 회사의 직책에 비유하면 이해하기 쉽다.

회사 직책 비유역할 (Role)인지 기능 (수행 작업)실체
부장Orchestrator전체 총괄 · 가벼운 분해 · 진척 관리메인 세션
...

먼저 밝혀두자면, "HR형 vs 기능형"과 같이 두 가지 구조를 대립시켜 선택했다는 이야기가 아니다.

직책 비유는 어디까지나 직관적인 입구일 뿐이며, 실체는 인지 기능으로 정의한다.

그렇다면 왜 직책명 그 자체를 사용하지 않고 인지 기능으로 정의하는가? 이유는 두 가지다.

1. 이름이 곧 "행동의 정의"가 되기 때문에 (상대가 LLM이기 때문)

에이전트에게 역할명과 설명은 곧 지시(Instruction)가 된다.

"고문"은 모호하지만, "Critic = 자기 보고를 믿지 않고, 실제 코드를 읽고 실행하여 사실 여부를 확인하는 검증 관문"이라고 기능으로 정의하면, 수행해야 할 동작이 구체적으로 고정된다.

직책명은 정의를 소홀히 할 수 있지만, 인지 기능은 "무엇을 생각하고, 무엇을 하며, 무엇을 하지 않을지"를 반드시 작성해야만 한다.

2. 암묵적인 계층 구조를 가져오지 않기 위해

직책은 "지위"이므로, 자칫 "인간 = 정점"이라는 계층 구조를 무의식적으로 이식하게 된다.

기능으로 정의하면, 인간을 "판단을 내리는 외부 오퍼레이터 (human-in-the-loop)"로서 자연스럽게 배치할 수 있다.

피라미드의 정점이 아니다.

게다가 기능명은 실행 모델에도 솔직하게 투영된다 ("생각하는 역할" = Planner/Critic, "움직이는 역할" = Worker, 대화적인 전반부 = Teams, 결정론적인 후반부 = Workflow).

직책 비유는 입구, 정의는 인지 기능으로 —— 라는 정리다.

하이브리드 제어 흐름: 전반부 Teams × 후반부 Workflow

역할(전절)을 두 가지 제어 흐름으로 나누어 운용한다.

  • 전반부 (이해 · 분해 · 설계 상담) = 동적인 Agent Teams
    → teammate를 spawn 하여, 대화하면서 방향을 결정한다. 인간이 개입할 수 있다.
  • 후반부 (구현 · 검증) = 결정론적인 Workflow
    → worklist (구현 태스크 목록)를 전달하여 Worker → Critic을 병렬 파이프라인으로 돌린다. 개입하지 않는 대신 **재현성 (Reproducibility)**이 있다.

다시 말해, 전반부에서 "무엇을 만들 것인가"를 대화하며 확정하여 태스크 목록 (worklist)을 만들고, 후반부에서 그것을 결정론적 파이프라인에 흘려보내 구현 및 검증을 수행한다.

핵심은 Critic 게이트

이 프레임워크에서 가장 중요한 것이 Critic이다. Worker의 자기 보고("완벽합니다")를 신뢰하지 않는다.

반드시 실제 파일을 Read 하고, 가능하다면 실제로 실행 및 테스트하여 동작으로 사실 여부를 확인한다. 보고 내용과 코드가 일치하지 않으면, 보고를 허위로 간주하여 불합격 처리한다.

실제로 테스트에서 "버그가 포함된 파일 + "정상적으로 작동합니다"라는 허위 보고"를 흘려보냈을 때, Critic은 실제로 코드를 구동하여 논리적 오류를 관측하고 근거를 바탕으로 불합격 처리했다.

"실행이 통과된다고 해서 반드시 옳은 것은 아니다"라는 점을 실측을 통해 잡아낼 수 있는 것이 이 게이트의 가치다.

플러그인으로 배포하기

Claude Code에는 플러그인 (Plugin) (배포 패키지)과 마켓플레이스 (Marketplace) (해당 배포처 카탈로그) 메커니즘이 있다.

하나의 리포지토리에 두 가지를 모두 공존시켰다.

team-framework/
├── .claude-plugin/marketplace.json ← 마켓플레이스 정의 (배포처)
├── plugins/team-framework/ ← 플러그인 본체
...

설치는 표준 명령어만 사용하면 된다:

# 카탈로그 등록 (owner/repo는 GitHub에서 해결됨)
/plugin marketplace add KoyanagiAyuha/team-framework
# 플러그인 설치 (plugin명@marketplace명)
...

user scope (즉, 해당 머신의 모든 프로젝트 공통)로 설치하면, 기동 옵션 없이 모든 프로젝트에서 유효하게 된다.

빠졌던 함정 (플러그인을 직접 만드는 사람을 위한 심화 내용)

여기서부터는 Claude Code 플러그인을 직접 만드는 사람을 위한 세세한 함정들이다. 설계 이야기만 알고 싶은 사람은 건너뛰어도 좋다.

권한 프롬프트를 "훅 (Hook)"을 통해 현명하게 줄이는 장까지 건너뛰는 것을 추천한다.

배포 형태로 만드는 과정에서, 문서만으로는 알 수 없거나 실측하지 않으면 확신할 수 없는 부분이 여러 군데 있었다.

${CLAU_PLUGIN_ROOT}

는 skill 본문에서는 전개되지 않는다

  1. 플러그인 내의 경로를 가리키는 변수 ${CLAUDE_PLUGIN_ROOT}

hook / MCP / LSP 등의 JSON 문자열에서만 전개된다. skill 본문이나 Workflow로부터 "플러그인 동봉 파일의 절대 경로"를 직접 참조하는 것은 불가능하다.

→ 대책: SessionStart hook (세션 시작 시 실행되는 hook)에서 Workflow 파일을 ~/.claude/workflows/ 로 복사한다. hook의 command 문자열이라면

${CLAUDE_PLUGIN_ROOT}

가 작동한다.

{
"hooks": {
"SessionStart": [{
...

~/.claude/workflows/

를 보지 않는다

  1. Workflow의 "이름 해결 (name resolution)"인 Workflow({ name: "worker-critic" })

는 내장된 + 한정된 세트만 해결하며, ~/.claude/workflows/ 에 두어도 "not found"가 된다.

→ 대책: scriptPath (절대 경로)로 호출한다.

Workflow({ scriptPath: "~/.claude/workflows/worker-critic.mjs", args: worklist })

즉, "(1) hook으로 동기화한 실체를, (2) 절대 경로로 호출한다"는 2단계 전략이 필요하다.

3. 플러그인의 agent / skill은 네임스페이스가 필수

bare name (순수 이름)으로는 해결되지 않는다. team-framework:planner/team-framework:team과 같이 플러그인 이름의 접두사 (prefix)를 붙여서 호출해야 한다.

Teammate로서의 spawn 또한 네임스페이스가 붙은 이름으로 성립한다 (이 부분은 문서화되어 있지 않아 실측을 통해 확인했다).

args

는 실기기에서 "문자열"로 전달되는 경우가 있다

  1. Workflow의 공식 문서에서는 "구조화된 데이터로 전달되므로 parse가 필요 없다"고 하지만, 실기기에서는 문자열로 전달되는 케이스가 있었다.

→ 스크립트 측에 JSON.parse 가드를 넣어 양쪽 모두 대응하도록 한다.

let worklist = args
if (typeof worklist === "string") {
try { worklist = JSON.parse(worklist) } catch { worklist = null }
...

5. worktree 격리에는 두 가지 전제가 있다

Worker가 스코프 외부를 오염시키는 것 (프로젝트 루트에 멋대로 파일을 만드는 등)을 방지하기 위해, 각 Worker를 git의 worktree (작업 트리를 별도 디렉토리로 분리하는 기능)로 격리하여 실행할 수 있다. 다만 실측 결과 두 가지 전제가 판명되었다.

  • 세션 시작 시점에 git 리포지토리일 것. 도중에 (git init을 해도 인식되지 않는 Cannot create agent worktree: not in a git repository 발생). 아무래도 세션 시작 시의 git 판정이 사용되는 듯하다.
  • 최소 1개의 커밋이 있을 것. worktree는 HEAD로부터 분기하므로, 커밋이 0개 (unborn HEAD)이면 Failed to resolve base branch "HEAD"로 실패한다.

→ 호출 측에서 "git 관리 하에 있는지", "커밋이 있는지"를 확인하고, 없다면 git commit --allow-empty -m init을 실행한 뒤에 기동하도록 명시했다.

권한 프롬프트를 "hook"으로 똑똑하게 줄이기

이것은 플러그인과는 별개로, Claude Code 전반에 적용되는 이야기다.

동기: 자율적으로 움직여야 하는데, 확인하느라 매달려 있게 된다

멀티 에이전트(Multi-agent) 환경에서는 특히 이 문제가 두드러진다.

공들여 Teams나 Workflow가 알아서 움직여 주는데, 각 에이전트가 명령어를 실행할 때마다 확인 프롬프트가 날아와서, 결국 오퍼레이터가 계속 "OK"를 누르고 있어야 하는 상황이 된다.

이래서는 자동화의 의미가 퇴색된다.

그렇다고 해서 전부 무확인 (위험 모드)으로 설정하고 싶지는 않다.

안전은 유지하면서, 정형화된 확인 작업만 없애고 싶다 —— 이것이 이 이야기의 동기다.

증상: 허가했는데도 매번 ask를 받는다

settings.json에 Bash(uv:*) (uv 명령어를 항상 허용)를 설정했음에도 불구하고, uv 계열 명령어를 사용할 때마다 매번 확인 요청이 발생한다. 원인은 uv가 아니라 명령어의 "형태"였다.

uv 전체 허용이 위험하지 않느냐는 문제는 여기서는 제쳐두기로 한다.

실제로 실행되고 있었던 것은 다음과 같은 복합 명령어였다:

cd /path && uv run python -c "..." 2>&1 | grep -v VIRTUAL_ENV

Claude Code는 &&, ||, ;, | 등을 해석하며, 복합 명령어(Compound Command)는 모든 서브 명령어(Sub-command)가 허용 규칙에 일치해야만 자동으로 승인한다. uv는 허용되어 있더라도, cdgrep이 미허용 상태이므로 중단되는 것이다.

더 까다로운 점은, cd + 출력 리다이렉션(Output Redirection)이 결합된 복합 명령어는 "경로 해석 우회(Path Resolution Bypass) 방지"를 위해 강제로 수동 승인을 받도록 설계되어 있다는 점이다. 이는 허용 규칙으로 덮어쓸 수 없다.

소박한 대처의 한계

Bash(cd:*)Bash(grep:*)를 추가하면 일부는 줄어든다. 하지만:

  • 헬퍼(Helper)를 계속 추가해야 하는 **끝없는 소모전(Cat-and-mouse game)**이 된다.
  • Bash(find:*)와 같이 광범위한 허용은 find -delete와 같은 위험한 형태까지 자동으로 승인해 버린다 (허용 규칙은 명령어의 내부 내용까지 확인하지 않기 때문이다).

정공법: PreToolUse 후크를 통해 "읽기 전용이라면 자동 승인"

공식 문서에서도 접두사(Prefix) 매칭의 한계를 넘고 싶다면 PreToolUse 후크를 권장한다. 후크는 명령어 전체(복합 명령어, 파이프, 히어 도큐먼트(Here-document) 포함)를 받을 수 있으므로, 자체적인 로직으로 판정할 수 있다.

방침은 다음과 같다:

  • 명령어를 구분자(&&, |, ; 등)로 분할하고, 모든 세그먼트가 읽기 전용(Read-only)임을 확신할 수 있을 때만 자동 승인(permissionDecision: "allow")한다.
  • 조금이라도 불분명하거나 위험한 경우(파일로의 출력 리다이렉션, 미지의 명령어, $() 등)에는 아무것도 반환하지 않고 그대로 통과시킨다. 즉, 기존 방식대로 확인 과정을 거치게 한다.
  • deny는 일절 반환하지 않는다. Claude Code 사양상 후크의 allowdeny / ask 규칙을 덮어쓰지 않으므로, 안전장치는 유지하면서 프롬프트만 줄이는 것이 가능하다.
# PreToolUse 후크의 핵심부 (개념)
def command_readonly(command: str) -> bool:
    if has_dangerous_meta(command): # $() / `` / <() 등
        ...

find -deletesort -o file과 같이 "읽기 방식처럼 보이지만 쓰는 형태"는 차단하고, find | xargs grep | headuv run python - <<'PY' … PY와 같은 실용적인 형태는 통과시킨다. 이렇게 하면 안전한 방향을 유지하면서도 실질적인 불편함(매번 발생하는 ask)을 제거할 수 있다.

다만 솔직히 말하자면, 이 방법으로 제거되는 것은 주로 "복합 명령어가 허용 규칙에 일치하지 않아서" 발생하는 ask이다.

앞서 언급한 'cd + 리다이렉션의 내장 가드'를 후크의 allow로 덮어쓸 수 있는지는 확인되지 않았다 (사양상 allow는 일부 내장 확인 절차를 덮어쓰지 않는다).

이 부분은 'cd를 복합 명령어에 섞지 않는다'는 운영 방식을 병행하는 것이 확실하다.

덤: Teams는 분할 표시되지만 Workflow는 되지 않는다

iTerm2 + tmux 환경에서 사용할 경우, Agent Teams의 teammate는 tmux 페인(Pane)으로 분할 표시된다 (teammateMode: "tmux", 서두의 이미지와 동일).

반면 Workflow는 백그라운드 실행되므로 분할되지 않으며, /workflows의 진행 상황 TUI(Text User Interface)를 통해 확인하는 방식이다.

표시 동작 자체는 사양이다: teammateMode가 적용되는 것은 Teams뿐이며, Workflow는 백그라운드 실행 + /workflows TUI로 확인한다는 점이 공식 문서상에서도 구분되어 있다.

한편 "왜 그런지"에 대해서는 명시되어 있지 않아 추측할 뿐이지만, 대화적으로 개입하는 Teams와 개입하지 않는 결정론적 배치(Deterministic Batch)인 Workflow라는 성격 차이를 고려하면 납득하기 쉽다.

Workflow는 중간에 개입하지 않는 것을 전제로 하므로, Pain(페인)에서 들여다볼 필연성은 적으며, 확인해야 할 것은 진척도와 반환값(합격/재계획 필요/불합격) 정도라고 파악하고 있다.

요약

  • 역할은 회사의 직책(부장/과장/담당/고문)에 비유하면 이해하기 쉽지만, 정의는 인지 기능(Cognitive Function)으로 수행한다. 그렇게 하면 행동이 구체적으로 고정되어 인간도 외부 오퍼레이터(Operator)로서 자연스럽게 배치할 수 있으며, Claude Code의 두 가지 실행 모델(동적 Teams / 결정론적 Workflow)에도 그대로 투영할 수 있다.
  • 전반부 Teams(동적) / 후반부 Workflow(결정론적)의 하이브리드 + **Critic의 실행 검증 게이트(Verification Gate)**가 핵심이다.
  • Claude Code의 **플러그인(Plugin) + 마켓플레이스(Marketplace)**를 통해 배포 가능하게 만들었다.
  • 함정의 대부분은 "문서의 설명과 실제 기기의 동작 사이의 간극"에 있었다. 결국 실제로 측정하여 로그에 남기는 것이 가장 빠르다.
  • 권한 프롬프트(Permission Prompt)는 허가 규칙을 잔뜩 넣기보다, **PreToolUse 후크(Hook)를 통해 "읽기 전용이라면 자동 승인"**하는 방식이 더 합리적이다 (안전장치는 유지하면서 마찰만 제거할 수 있다).

마지막으로, 반복되는 이야기지만 이미 플러그인을 공개했으므로 꼭 이용해 보길 바란다.

리포지토리: https://github.com/KoyanagiAyuha/team-framework

Discussion

AI 자동 생성 콘텐츠

본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0