시리즈 이전 편: [Part 8] QueryEngine 분석
목차
- AI가 혼자 못 풀면 다른 AI를 불러온다
- AgentTool — 서브에이전트를 만드는 공장
- 프롬프트 전달 — "방금 들어온 동료에게 브리핑하듯"
- SendMessageTool — 에이전트 간 메시지 전달
- 코디네이터 모드 — "병렬성이 너의 초능력이다"
- 실제 사용 예시 — simplify 스킬의 3-에이전트 병렬 리뷰
- 그래서 뭐가 달라지나 — 멀티에이전트 설계 패턴
AI가 혼자 못 풀면 다른 AI를 불러온다
복잡한 코드 리뷰를 혼자 하려면 시간이 오래 걸립니다. 사람이라면 동료를 불러서 나눠 보겠죠. Claude Code도 같은 전략을 씁니다. 혼자 처리하기 버거운 작업이 오면 서브에이전트를 생성해서 병렬로 돌립니다. 이번 편에서는 그 구조가 실제 코드에서 어떻게 구현되어 있는지 뜯어봅니다.
AgentTool — 서브에이전트를 만드는 공장
서브에이전트의 시작점은 AgentTool입니다. 입력 스키마부터 보겠습니다.
// src/tools/AgentTool/AgentTool.tsx (line 82-88)
const baseInputSchema = lazySchema(() => z.object({
description: z.string().describe('A short (3-5 word) description of the task'),
prompt: z.string().describe('The task for the agent to perform'),
subagent_type: z.string().optional().describe('The type of specialized agent to use'),
model: z.enum(['sonnet', 'opus', 'haiku']).optional(),
run_in_background: z.boolean().optional()
}));
description, prompt, subagent_type이 핵심입니다. 어떤 유형의 에이전트를 띄울지 지정하고, 프롬프트로 작업을 위임합니다. 여기에 isolation 옵션으로 git worktree 격리까지 걸 수 있습니다. 서브에이전트가 본체 작업 디렉토리를 건드리지 못하게 분리하는 겁니다.
도구 제한도 중요합니다. prompt.ts에서 에이전트별로 사용 가능한 도구 목록을 관리합니다.
// src/tools/AgentTool/prompt.ts (line 15-37)
function getToolsDescription(agent: AgentDefinition): string {
const { tools, disallowedTools } = agent
if (hasAllowlist && hasDenylist) {
const denySet = new Set(disallowedTools)
const effectiveTools = tools.filter(t => !denySet.has(t))
return effectiveTools.join(', ')
} else if (hasAllowlist) {
return tools.join(', ')
}
return 'All tools'
}
allowlist/denylist 조합으로 에이전트마다 쓸 수 있는 도구가 다릅니다. 리서치 에이전트에게는 파일 수정 도구를 빼고, 구현 에이전트에게는 전체를 열어주는 식입니다. 권한 분리의 기본이죠.
프롬프트 전달 — "방금 들어온 동료에게 브리핑하듯"
서브에이전트는 부모의 대화 컨텍스트를 모릅니다. 그래서 프롬프트 가이드가 이렇게 말합니다.
// src/tools/AgentTool/prompt.ts (line 103)
Brief the agent like a smart colleague who just walked into the room
— it hasn't seen this conversation, doesn't know what you've tried,
doesn't understand why this task matters.
"방 안에 막 들어온 똑똑한 동료"에게 브리핑하라는 겁니다. 파일 경로, 라인 번호, 시도한 내용을 다 넘겨야 합니다. fork 모드를 쓰면 부모 컨텍스트를 그대로 상속하지만, 일반 서브에이전트는 백지 상태에서 시작합니다.
SendMessageTool — 에이전트 간 메시지 전달
에이전트끼리 통신하는 도구가 SendMessageTool입니다. 스키마가 간결합니다.
// src/tools/SendMessageTool/SendMessageTool.ts (line 67-87)
const inputSchema = lazySchema(() =>
z.object({
to: z.string().describe('Recipient: teammate name, or "*" for broadcast'),
summary: z.string().optional().describe('A 5-10 word summary shown as a preview'),
message: z.union([
z.string().describe('Plain text message content'),
StructuredMessage(),
]),
}),
)
to에 에이전트 이름이나 ID를 넣고, message로 내용을 보냅니다. "*"를 넣으면 브로드캐스트입니다. 핵심은 prompt.ts에 적힌 이 문장입니다: "Your plain text output is NOT visible to other agents -- to communicate, you MUST call this tool." 일반 텍스트 출력은 다른 에이전트에게 안 보입니다. 반드시 이 도구를 거쳐야 합니다.
작업이 끝난 에이전트를 다시 이어서 쓸 수도 있습니다. SendMessage로 해당 에이전트의 ID에 후속 지시를 보내면, 이전 컨텍스트가 살아있는 상태로 재개됩니다.
코디네이터 모드 — "병렬성이 너의 초능력이다"
coordinatorMode.ts에는 코디네이터 전용 시스템 프롬프트가 통째로 들어 있습니다. 가장 인상적인 부분은 이겁니다.
// src/coordinator/coordinatorMode.ts (line 213)
Parallelism is your superpower. Workers are async.
Launch independent workers concurrently whenever possible
— don't serialize work that can run simultaneously
and look for opportunities to fan out.
"병렬성이 너의 초능력이다." 코디네이터의 역할을 한 문장으로 정의합니다. 코디네이터는 직접 코드를 안 만집니다. 대신 워커를 띄우고, 결과를 취합하고, 사용자에게 보고합니다.
작업 흐름은 4단계로 구분됩니다: Research(워커 병렬) -> Synthesis(코디네이터) -> Implementation(워커) -> Verification(워커). 코디네이터가 직접 해야 하는 건 Synthesis뿐입니다. 워커 결과를 읽고 이해한 뒤, 구체적인 구현 지시를 작성하는 것. "based on your findings, fix the bug" 같은 떠넘기기를 명시적으로 금지합니다.
동시성 관리 규칙도 명확합니다. 읽기 작업은 자유롭게 병렬, 쓰기 작업은 파일 단위로 직렬, 검증은 구현과 파일 영역이 다르면 병렬 가능. 이건 사람 팀에서도 똑같은 규칙입니다.
실제 사용 예시 — simplify 스킬의 3-에이전트 병렬 리뷰
이론이 아니라 실제로 쓰이는 곳을 봅시다. simplify 스킬은 코드 리뷰를 3개 에이전트에게 동시에 맡깁니다.
// src/skills/bundled/simplify.ts (line 13-14)
// Phase 2: Launch Three Review Agents in Parallel
// Use the Agent tool to launch all three agents
// concurrently in a single message.
Agent 1은 코드 재사용 검토(기존 유틸리티와 중복 여부), Agent 2는 코드 품질 검토(복붙 패턴, 불필요한 주석), Agent 3은 효율성 검토(N+1 패턴, 불필요한 존재 확인). 세 에이전트가 같은 diff를 받아서 각자 관점으로 동시에 분석합니다.
세 에이전트의 결과가 돌아오면 Phase 3에서 종합해서 실제 수정까지 합니다. 거짓 양성은 넘기고, 진짜 문제만 고칩니다. 혼자 순차적으로 했으면 3배 걸렸을 작업을 병렬로 처리하는 겁니다.
그래서 뭐가 달라지나 — 멀티에이전트 설계 패턴
Claude Code의 멀티에이전트 구조에서 눈여겨볼 패턴을 정리합니다.
컨텍스트 격리: 서브에이전트는 부모 대화를 못 봅니다. 이건 의도된 설계입니다. 불필요한 컨텍스트가 판단을 흐리는 걸 막습니다. fork 모드는 예외인데, 프롬프트 캐시를 공유해서 비용을 아끼려는 목적입니다.
도구 제한으로 역할 분리: allowlist/denylist로 에이전트별 권한을 나눕니다. 리서치 에이전트가 파일을 수정하거나, 구현 에이전트가 사용자와 직접 대화하는 일을 구조적으로 차단합니다.
메시지 기반 통신: 에이전트 간 직접 메모리 공유 없이 SendMessageTool로만 소통합니다. 느슨한 결합이고, 에이전트를 독립적으로 재시작하거나 교체할 수 있는 구조입니다.
코디네이터는 이해를 위임하지 않는다: "Never write 'based on your findings'" 원칙이 인상적입니다. 사람 팀에서도 PM이 개발자 보고를 안 읽고 "알아서 고쳐"라고 하면 문제가 생기죠. AI 시스템도 같습니다.
이 패턴들은 Claude Code에만 국한되지 않습니다. LLM 기반 멀티에이전트 시스템을 설계할 때 그대로 적용할 수 있는 원칙들입니다.
다음 편 예고: [Claude Code 해부학 Part 10] 클로드 코드(Claude Code) 권한 시스템 분석 — rm -rf를 못 치게 막는 법
'AI 코딩 에이전트' 카테고리의 다른 글
| Claude Code MCP 구현 분석: AI의 USB 포트를 직접 만들어봤다 (해부학 Part 11) (0) | 2026.04.02 |
|---|---|
| Claude Code 권한 시스템 분석: rm -rf를 못 치게 막는 법 (해부학 Part 10) (0) | 2026.04.01 |
| Claude Code QueryEngine 분석: AI와 대화하는 코드의 정체 (해부학 Part 8) (0) | 2026.04.01 |
| Claude Code 도구 시스템 분석: AI에게 손발을 달아주는 40개 Tool (해부학 Part 6) (0) | 2026.04.01 |
| Claude Code 동적 프롬프트 분석: 매번 다르게 말하는 비밀 (해부학 Part 5) (0) | 2026.04.01 |
