claude-code/docs/ko/level-2-systems/agent-coordinator.md
JuYoung Jeong 3358348196 docs: add comprehensive Korean source code analysis (19 documents, 6,926 lines)
Add complete Korean-language technical documentation analyzing Claude Code CLI
internals, organized in a leveled guide structure (Level 1-3 + Appendix).

Level 1 (입문): architecture overview, request lifecycle, key concepts
Level 2 (시스템): QueryEngine, Tool system, Command system, Permission system,
  Agent coordinator, UI/Ink components
Level 3 (심화): MCP/LSP integration, IDE bridge, Plugin/Skill system,
  Context compression, OAuth/Auth, Telemetry
Appendix: Korean-English glossary, file map, design patterns

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:39:03 +09:00

27 KiB

에이전트 오케스트레이션: 멀티에이전트 시스템 분석

1. 개요

Claude Code의 멀티에이전트(multi-agent) 오케스트레이션(orchestration) 시스템은 단일 세션 안에서 여러 서브에이전트(subagent)를 동시에 생성하고 조율하는 기반 구조다. 이 시스템은 복잡한 작업을 독립적인 단위로 분해하고 병렬 실행하여 성능과 컨텍스트 격리(context isolation)를 동시에 달성한다.

핵심 구성 요소와 파일 위치:

구성 요소 경로
Coordinator Mode (코디네이터 모드) src/coordinator/coordinatorMode.ts
AgentTool (에이전트 생성 도구) src/tools/AgentTool/
TeamCreateTool / TeamDeleteTool src/tools/TeamCreateTool/, src/tools/TeamDeleteTool/
SendMessageTool (에이전트 간 통신) src/tools/SendMessageTool/
Task 시스템 src/tasks/

이 문서는 각 구성 요소의 구현 세부 사항과 상호작용 방식을 분석한다.


2. 아키텍처 다이어그램

전체 시스템의 데이터 흐름은 다음과 같다.

graph TD
    User["사용자"] --> MainAgent["메인 에이전트\n(Coordinator Mode)"]

    MainAgent -->|"AgentTool(subagent_type, prompt)"| Spawn["에이전트 생성 경로"]
    Spawn -->|"일반 서브에이전트"| LocalAgentTask["LocalAgentTask\n(로컬 비동기 실행)"]
    Spawn -->|"isolation: worktree"| Worktree["Git Worktree\n(격리된 작업 복사본)"]
    Spawn -->|"isolation: remote (ant-only)"| RemoteAgentTask["RemoteAgentTask\n(원격 CCR 실행)"]

    LocalAgentTask --> SubAgent["서브에이전트\n(독립 QueryEngine + 컨텍스트)"]
    Worktree --> SubAgent

    SubAgent -->|"task-notification XML"| MainAgent

    MainAgent -->|"TeamCreateTool(team_name)"| TeamContext["팀 컨텍스트\n(TeamFile)"]
    TeamContext -->|"AgentTool(subagent_type, team_name)"| Teammate["팀메이트(Teammate)\n(tmux/in-process)"]
    Teammate -->|"SendMessageTool(to, message)"| Mailbox["메일박스(Mailbox)\n파일시스템 기반"]
    Mailbox -->|"수신"| Teammate
    Mailbox -->|"수신"| MainAgent

    MainAgent -->|"SendMessageTool(to: agentId)"| SubAgent
    MainAgent -->|"TeamDeleteTool()"| Cleanup["팀 정리\n(디렉터리, 컬러, 태스크)"]

    SubAgent -.->|"ProgressTracker 업데이트"| TaskProgress["태스크 진행 상황\n(토큰 수, 도구 호출 수)"]
    TaskProgress -.-> MainAgent

    style MainAgent fill:#2d6a9f,color:#fff
    style SubAgent fill:#3a7d44,color:#fff
    style LocalAgentTask fill:#7d5a3c,color:#fff
    style RemoteAgentTask fill:#7d3a3a,color:#fff
    style TeamContext fill:#5a3a7d,color:#fff

핵심 설계 원칙: 모든 서브에이전트는 자신만의 독립적인 QueryEngine 인스턴스와 메시지 컨텍스트를 가진다. 부모 에이전트는 서브에이전트의 내부 상태를 직접 접근할 수 없으며, 오직 task-notification XML 메시지를 통해 결과를 수신한다.


3. Coordinator Mode (코디네이터 모드)

3.1 기본 동작: isCoordinatorMode()

// src/coordinator/coordinatorMode.ts
export function isCoordinatorMode(): boolean {
  if (feature('COORDINATOR_MODE')) {
    return isEnvTruthy(process.env.CLAUDE_CODE_COORDINATOR_MODE)
  }
  return false
}

코디네이터 모드는 두 겹의 게이팅(gating)을 통해 활성화된다.

  1. 피처 플래그(feature flag) 게이트: feature('COORDINATOR_MODE') — Bun 번들러의 bun:bundle 모듈이 제공하는 빌드 타임 상수다. 플래그가 false이면 번들러가 이 코드 경로 전체를 데드 코드(dead code)로 제거한다.
  2. 환경 변수 게이트: CLAUDE_CODE_COORDINATOR_MODE 환경 변수가 truthy 값으로 설정되어야 한다.

이중 게이트 구조는 프로덕션 빌드에서 멀티에이전트 기능 코드를 완전히 제거할 수 있도록 한다.

3.2 세션 재개 시 모드 일치: matchSessionMode()

export function matchSessionMode(
  sessionMode: 'coordinator' | 'normal' | undefined,
): string | undefined

저장된 세션을 재개할 때 현재 환경의 모드와 세션에 기록된 모드가 다를 수 있다. 이 함수는 process.env.CLAUDE_CODE_COORDINATOR_MODE를 런타임에 직접 수정하여 불일치를 해소한다. isCoordinatorMode()가 환경 변수를 캐시 없이 매번 직접 읽기 때문에 이 방식이 작동한다.

3.3 워커 도구 컨텍스트: getCoordinatorUserContext()

코디네이터 모드가 활성화되면 메인 에이전트는 워커(worker) 에이전트들이 어떤 도구에 접근할 수 있는지 알아야 한다. getCoordinatorUserContext()는 이 정보를 workerToolsContext 키로 반환한다.

// 내부 도구 집합 (워커에게 노출되지 않음)
const INTERNAL_WORKER_TOOLS = new Set([
  TEAM_CREATE_TOOL_NAME,
  TEAM_DELETE_TOOL_NAME,
  SEND_MESSAGE_TOOL_NAME,
  SYNTHETIC_OUTPUT_TOOL_NAME,
])

CLAUDE_CODE_SIMPLE 환경 변수가 설정된 경우 워커 도구를 Bash, Read, Edit 세 가지로만 제한하는 단순 모드도 지원한다.

3.4 코디네이터 시스템 프롬프트

getCoordinatorSystemPrompt()는 다음 단계로 구성된 멀티에이전트 워크플로를 코디네이터에게 지시하는 시스템 프롬프트를 생성한다.

단계 담당 목적
Research (조사) 워커 (병렬) 코드베이스 탐색, 문제 파악
Synthesis (종합) 코디네이터 발견 사항 읽기, 구현 명세 작성
Implementation (구현) 워커 변경 적용, 커밋
Verification (검증) 워커 변경 사항 테스트

핵심 원칙은 **"절대로 이해를 위임하지 말라(Never delegate understanding)"**이다. 코디네이터는 워커의 결과를 직접 종합하여 다음 워커에게 구체적인 파일 경로, 줄 번호, 변경 내용을 포함한 지시를 작성해야 한다.

3.5 Scratchpad (스크래치패드) 게이트

function isScratchpadGateEnabled(): boolean {
  return checkStatsigFeatureGate_CACHED_MAY_BE_STALE('tengu_scratch')
}

tengu_scratch 피처 게이트가 활성화되면 워커들이 퍼미션 프롬프트 없이 읽고 쓸 수 있는 공유 스크래치패드 디렉터리가 활성화된다. 스크래치패드는 워커 간 지식을 영속적으로 공유하는 메커니즘이다.

주목할 점은 isScratchpadGateEnabled()src/utils/permissions/filesystem.tsisScratchpadEnabled()와 동일한 게이트를 검사하지만 함수를 재사용하지 않는다는 것이다. 이는 filesystem.ts를 가져오면 filesystem → permissions → ... → coordinatorMode의 순환 의존성(circular dependency)이 발생하기 때문이다. 이 설계 결정은 코드 주석에 명시되어 있다.


4. AgentTool 분석

4.1 에이전트 생성 메커니즘

AgentTool.tsx는 새 에이전트를 생성하는 핵심 도구다. 입력 스키마(input schema)는 피처 플래그에 따라 동적으로 구성된다.

// 기본 입력 파라미터
const baseInputSchema = lazySchema(() => z.object({
  description: z.string(),      // 3-5 단어의 짧은 작업 설명
  prompt: z.string(),           // 에이전트에게 전달할 실제 지시
  subagent_type: z.string().optional(),  // 전문 에이전트 타입
  model: z.enum(['sonnet', 'opus', 'haiku']).optional(),
  run_in_background: z.boolean().optional(),
}));

KAIROS 피처 플래그가 활성화된 경우 cwd 파라미터(작업 디렉터리 재정의)가 추가된다. run_in_backgroundCLAUDE_CODE_DISABLE_BACKGROUND_TASKS 환경 변수 또는 Fork Subagent 기능이 활성화된 경우 스키마에서 제거된다.

자동 백그라운드 타임아웃:

function getAutoBackgroundMs(): number {
  if (isEnvTruthy(process.env.CLAUDE_AUTO_BACKGROUND_TASKS) ||
      getFeatureValue_CACHED_MAY_BE_STALE('tengu_auto_background_agents', false)) {
    return 120_000;  // 2분 후 자동으로 백그라운드로 전환
  }
  return 0;
}

포그라운드(foreground)로 실행 중인 에이전트가 2분을 초과하면 자동으로 백그라운드로 전환된다.

4.2 AgentDefinition 타입 계층

에이전트 정의는 세 가지 소스에서 로드된다.

AgentDefinition (유니언 타입)
├── BuiltInAgentDefinition   (source: 'built-in')
│   └── getSystemPrompt(params) — toolUseContext 접근 가능
├── CustomAgentDefinition    (source: 'userSettings' | 'projectSettings' | ...)
│   └── getSystemPrompt()    — 클로저(closure)로 마크다운 내용 캡처
└── PluginAgentDefinition    (source: 'plugin')
    └── getSystemPrompt()    — 플러그인 메타데이터 포함

getSystemPrompt가 함수인 이유는 메모리(memory) 기능이 활성화된 경우 에이전트 메모리 프롬프트를 동적으로 결합해야 하기 때문이다. 정적 문자열이 아닌 클로저를 사용하여 이를 구현한다.

에이전트 우선순위 (덮어쓰기 순서):

built-in < plugin < userSettings < projectSettings < flagSettings < policySettings

동일한 agentType 이름이 여러 소스에 존재하면 우선순위가 높은 소스가 낮은 소스를 덮어쓴다. 예를 들어 프로젝트 설정의 커스텀 에이전트는 빌트인 에이전트를 같은 이름으로 오버라이드할 수 있다.

4.3 에이전트 로딩: loadAgentsDir.ts

getAgentDefinitionsWithOverrides(cwd)lodash-es/memoize로 메모화(memoize)되어 동일한 작업 디렉터리에 대해 한 번만 파일시스템을 읽는다.

마크다운 에이전트 파일 프론트매터(frontmatter) 구조:

---
name: my-agent
description: 이 에이전트를 사용할 때
model: sonnet
effort: high
permissionMode: default
isolation: worktree
memory: user
background: false
maxTurns: 50
tools: [Bash, Read, Edit]
---
에이전트 시스템 프롬프트 내용...

parseAgentFromMarkdown()은 다음을 수행한다.

  • isolation: 'remote'USER_TYPE === 'ant'(Anthropic 내부 빌드)에서만 허용한다.
  • memory 필드가 설정되면 FILE_WRITE_TOOL_NAME, FILE_EDIT_TOOL_NAME, FILE_READ_TOOL_NAME을 도구 목록에 자동 주입한다.
  • HooksSchema로 훅(hook)을 파싱한다. HooksSchema가 lazy인 이유는 AppState → loadAgentsDir → settings/types의 순환 의존성을 모듈 로드 시점에 끊기 위해서다.

4.4 Fork Subagent (포크 서브에이전트)

forkSubagent.ts는 부모 에이전트의 전체 대화 컨텍스트를 상속받는 자식 에이전트를 생성하는 실험적 기능을 구현한다.

export function isForkSubagentEnabled(): boolean {
  if (feature('FORK_SUBAGENT')) {
    if (isCoordinatorMode()) return false  // 코디네이터와 상호 배타적
    if (getIsNonInteractiveSession()) return false
    return true
  }
  return false
}

포크 메커니즘의 핵심: 프롬프트 캐시 공유

모든 포크 자식은 바이트 동일(byte-identical)한 API 요청 접두사를 생성해야 캐시 히트(cache hit)가 가능하다. buildForkedMessages()는 이를 위해 다음 구조를 만든다.

[...부모 히스토리, assistant(모든_tool_use 블록), user(플레이스홀더_results..., 자식별_지시문)]

모든 tool_result 블록에 동일한 플레이스홀더 텍스트 "Fork started — processing in background"를 사용하고, 오직 마지막 텍스트 블록만 자식별로 다르게 함으로써 캐시 히트율을 극대화한다.

포크 자식 규칙 (buildChildMessage에서 강제):

포크 자식 메시지는 <fork-boilerplate> 태그로 시작하며 다음을 강제한다.

  • 서브에이전트를 재귀적으로 생성하지 말 것
  • 도구 호출 사이에 텍스트를 출력하지 말 것
  • 응답은 반드시 "Scope:"로 시작할 것
  • 파일을 수정한 경우 커밋 후 해시를 보고할 것

재귀 포크 방지는 isInForkChild()가 대화 히스토리에서 <fork-boilerplate> 태그를 탐지하는 방식으로 구현된다.

4.5 에이전트 실행: runAgent.ts

runAgent.ts는 서브에이전트의 전체 실행 수명주기(lifecycle)를 관리한다.

에이전트 전용 MCP 서버 초기화:

에이전트 정의의 프론트매터에 mcpServers가 지정된 경우, initializeAgentMcpServers()가 부모의 MCP 클라이언트에 추가로 에이전트 전용 서버를 연결한다. 에이전트 종료 시 정리 함수가 호출되어 연결을 해제한다.

파일 상태 캐시 상속:

서브에이전트는 부모의 파일 상태 캐시를 cloneFileStateCache()로 복제하여 시작한다. 이를 통해 부모가 이미 읽은 파일 정보를 서브에이전트가 재활용할 수 있다.

도구 풀(tool pool) 구성:

resolveAgentTools()는 에이전트 정의의 tools(허용 목록) 및 disallowedTools(거부 목록)을 부모의 도구 풀에 적용하여 서브에이전트의 최종 도구 집합을 결정한다.

4.6 에이전트 색상 관리

멀티에이전트 UI에서 각 에이전트를 시각적으로 구별하기 위해 agentColorManager.ts가 에이전트 타입별 색상을 관리한다. 에이전트 정의의 color 필드나 자동 할당을 통해 색상이 지정된다.


5. 팀 시스템

5.1 TeamCreateTool

TeamCreateTool은 에이전트 스웜(swarm) 기능이 활성화된 경우(isAgentSwarmsEnabled())에만 사용 가능하다.

// 팀 생성 입력 파라미터
z.strictObject({
  team_name: z.string(),        // 팀 이름
  description: z.string().optional(),
  agent_type: z.string().optional(), // 팀 리더의 역할 타입
})

팀 생성 시 수행 작업:

  1. 팀 파일 생성: TeamFile 구조를 파일시스템에 기록한다. 동일 이름의 팀이 이미 존재하면 새로운 고유 이름을 자동 생성한다.
  2. 태스크 디렉터리 초기화: resetTaskList()ensureTasksDir()로 팀 전용 태스크 목록 디렉터리를 생성한다. 팀마다 태스크 번호가 1부터 시작한다.
  3. 리더 팀 이름 등록: setLeaderTeamName()을 호출하여 getTaskListId()가 세션 ID 대신 팀 이름을 반환하도록 한다.
  4. AppState 업데이트: teamContext를 설정하여 현재 세션이 팀 컨텍스트를 인식하게 한다.
  5. 세션 정리 등록: registerTeamForSessionCleanup()으로 세션 종료 시 팀 디렉터리가 자동 정리되도록 한다.

리더(leader)와 팀메이트(teammate) 구분:

팀 리더는 CLAUDE_CODE_AGENT_ID 환경 변수를 설정하지 않는다. 이를 통해 isTeammate()가 리더에 대해 false를 반환하고, 인박스(inbox) 폴링(polling)을 비롯한 팀메이트 전용 동작이 리더에게는 적용되지 않는다.

5.2 TeamDeleteTool

팀 삭제는 입력 파라미터가 없는 단순한 정리 작업이다.

삭제 전 안전 검사:

const activeMembers = nonLeadMembers.filter(m => m.isActive !== false)
if (activeMembers.length > 0) {
  // 활성 멤버가 있으면 삭제 거부
}

isActive !== false로 판단하는 이유는 isActive가 정의되지 않은 멤버(undefined)를 활성 상태로 간주하기 때문이다. false로 명시적으로 설정된 경우에만 유휴/종료 상태로 판단한다.

정리 순서:

  1. 팀 파일 및 관련 디렉터리 삭제 (cleanupTeamDirectories)
  2. 세션 정리 목록에서 제거 (unregisterTeamForSessionCleanup)
  3. 에이전트 색상 할당 초기화 (clearTeammateColors)
  4. 리더 팀 이름 초기화 (clearLeaderTeamName)
  5. AppState에서 teamContextinbox 제거

5.3 SendMessageTool (에이전트 간 통신)

SendMessageTool은 여러 통신 경로를 단일 인터페이스로 통합한다.

메시지 라우팅 결정 트리:

SendMessageTool(to, message)
├── to == "bridge:<session-id>"  →  원격 제어 세션 (사용자 승인 필요)
├── to == "uds:<socket-path>"    →  로컬 UDS 소켓 전송
├── to == "*"                    →  팀 전체 브로드캐스트
├── to == agentId (로컬 태스크)  →  queuePendingMessage() 또는 resumeAgentBackground()
└── to == teammateName           →  writeToMailbox() (파일시스템 기반)

인박스 기반 통신의 특성:

writeToMailbox()는 파일시스템의 팀 디렉터리에 메시지를 기록한다. 수신자는 자신의 인박스를 주기적으로 폴링하여 메시지를 처리한다. 이 비동기 방식은 tmux 세션이나 별도 프로세스로 실행되는 팀메이트와의 통신에 적합하다.

로컬 서브에이전트 메시지 큐잉:

로컬 에이전트 태스크(LocalAgentTask)가 실행 중인 경우 메시지를 즉시 전달하는 것이 아니라 queuePendingMessage()로 큐에 추가한다. 에이전트는 다음 도구 호출 라운드에서 큐의 메시지를 처리한다.

정지된 에이전트 자동 재개:

에이전트가 정지 상태(stopped)인 경우 SendMessageTool이 자동으로 resumeAgentBackground()를 호출하여 에이전트를 재개한다. 에이전트가 태스크 레지스트리에서 제거된 경우에도 디스크의 트랜스크립트(transcript)에서 복원할 수 있다.

구조화 메시지(structured message) 타입:

  • shutdown_request: 팀메이트에게 종료 요청 전송
  • shutdown_response (approve/reject): 종료 요청에 대한 응답
  • plan_approval_response: 플랜 모드(plan mode)에서 팀 리더의 승인/거부

6. 태스크 시스템

6.1 태스크 타입 계층

src/tasks/types.ts에 정의된 TaskState 유니언 타입은 모든 태스크 타입을 포함한다.

export type TaskState =
  | LocalShellTaskState         // 로컬 셸 태스크 (Bash 실행)
  | LocalAgentTaskState         // 로컬 에이전트 태스크 (AgentTool 생성)
  | RemoteAgentTaskState        // 원격 에이전트 태스크 (CCR)
  | InProcessTeammateTaskState  // 인-프로세스 팀메이트
  | LocalWorkflowTaskState      // 로컬 워크플로 태스크
  | MonitorMcpTaskState         // MCP 모니터 태스크
  | DreamTaskState              // Dream 태스크

6.2 LocalAgentTask: 로컬 에이전트 실행

LocalAgentTaskAgentTool로 생성된 서브에이전트의 비동기 실행을 관리한다.

진행 상황 추적 (ProgressTracker):

export type ProgressTracker = {
  toolUseCount: number;
  latestInputTokens: number;      // 누적 입력 (API가 매 턴 누적값 반환)
  cumulativeOutputTokens: number; // 매 턴 출력 토큰의 합산
  recentActivities: ToolActivity[];
};

입력 토큰과 출력 토큰을 별도로 추적하는 이유는 Claude API의 특성 때문이다. input_tokens는 매 요청마다 이전 컨텍스트를 포함한 누적값을 반환하므로 최신값을 유지하고, output_tokens는 턴별 값이므로 누산해야 한다.

활동 설명 해석기 (ActivityDescriptionResolver):

도구 이름과 입력을 받아 "src/foo.ts 읽기"와 같은 사람이 읽을 수 있는 활동 설명을 반환하는 함수다. 도구의 getActivityDescription() 메서드를 호출하여 생성하며, 최근 5개의 활동을 유지한다.

태스크 완료 알림:

에이전트 실행이 완료되면 다음 구조의 XML을 생성하여 부모에게 알린다.

<task-notification>
  <task-id>{agentId}</task-id>
  <status>completed|failed|killed</status>
  <summary>{상태 요약}</summary>
  <result>{에이전트의 최종 텍스트 응답}</result>
  <usage>
    <total_tokens>N</total_tokens>
    <tool_uses>N</tool_uses>
    <duration_ms>N</duration_ms>
  </usage>
</task-notification>

6.3 RemoteAgentTask: 원격 실행

RemoteAgentTask는 원격 CCR(Claude Code Remote) 환경에서 실행되는 에이전트를 관리한다.

const REMOTE_TASK_TYPES = [
  'remote-agent',    // 일반 원격 에이전트
  'ultraplan',       // 대형 계획 수립
  'ultrareview',     // 코드 리뷰
  'autofix-pr',      // PR 자동 수정
  'background-pr',   // 백그라운드 PR 처리
] as const;

원격 태스크는 pollRemoteSessionEvents()로 원격 세션의 이벤트를 폴링하며, 완료 시 archiveRemoteSession()을 호출한다. RemoteTaskCompletionChecker 콜백을 등록하여 태스크 타입별 완료 조건을 외부에서 주입할 수 있다.

6.4 백그라운드 태스크 판별

export function isBackgroundTask(task: TaskState): task is BackgroundTaskState {
  if (task.status !== 'running' && task.status !== 'pending') {
    return false
  }
  if ('isBackgrounded' in task && task.isBackgrounded === false) {
    return false  // 포그라운드 태스크는 백그라운드 표시기에 나타나지 않음
  }
  return true
}

isBackgrounded === false(명시적 false)와 isBackgrounded === undefined(기본값)를 구분하는 것이 중요하다. undefined인 태스크는 백그라운드로 간주한다.


7. 주요 설계 결정

7.1 피처 플래그를 통한 코디네이터 모드 격리

멀티에이전트 기능은 feature('COORDINATOR_MODE') 빌드 타임 플래그로 전체 코드 경로를 조건부 컴파일한다. 이는 단순히 런타임 기능 토글이 아닌 **데드 코드 제거(dead code elimination)**를 위한 선택이다.

AgentTool.tsx의 팀메이트 관련 코드도 동일한 패턴을 사용한다.

// 타입 정의는 TypeScript 컴파일 시 제거되므로 허용
type TeammateSpawnedOutput = { ... }  // 타입은 괜찮음

// 런타임 상수는 인라인 게이트 블록 내부에서만 정의
// "Multi-agent type constants are defined inline inside gated blocks
//  to enable dead code elimination"

이 접근법은 멀티에이전트 기능이 비활성화된 빌드에서 번들 크기와 공격 표면(attack surface)을 최소화한다.

7.2 순환 의존성 방지를 위한 Lazy require()

AgentTool.tsx에서 Proactive 모듈을 동적으로 로드하는 패턴이 사용된다.

const proactiveModule = feature('PROACTIVE') || feature('KAIROS')
  ? require('../../proactive/index.js') as typeof import('../../proactive/index.js')
  : null;

정적 import 대신 런타임 require()를 사용하는 것은 모듈 로드 시점의 순환 의존성을 끊기 위해서다. HooksSchema의 lazy 선언, scratchpad 게이트의 중복 구현도 같은 이유에서 비롯된다.

7.3 에이전트 격리: 컨텍스트 독립성

각 서브에이전트는 다음을 독립적으로 소유한다.

  • 자체 QueryEngine 인스턴스
  • 부모로부터 복제된 파일 상태 캐시
  • 에이전트 정의에 명시된 도구 집합
  • (선택적) 격리된 Git Worktree

이 격리는 서브에이전트가 부모 컨텍스트를 오염시키거나 간섭하는 것을 방지한다. 부모와 자식 간의 유일한 통신 채널은 task-notification XML 메시지다.

7.4 Worktree 통합

isolation: 'worktree'가 지정된 에이전트는 createAgentWorktree()로 임시 Git Worktree를 생성한다. 에이전트 종료 시:

  • 변경 사항이 없으면 removeAgentWorktree()로 즉시 정리
  • 변경 사항이 있으면 Worktree 경로와 브랜치를 결과에 포함하여 반환

이를 통해 병렬로 실행되는 에이전트들이 서로의 파일 변경에 간섭하지 않는다.

7.5 ONE_SHOT_BUILTIN_AGENT_TYPES 최적화

export const ONE_SHOT_BUILTIN_AGENT_TYPES: ReadonlySet<string> = new Set([
  'Explore',
  'Plan',
])

ExplorePlan 에이전트는 한 번 실행하고 결과를 보고하는 단발성 에이전트다. 이 에이전트들의 결과에는 agentId/SendMessage/usage 트레일러를 생략한다. 주석에 따르면 이를 통해 에이전트당 약 135자를 절약하며, 주당 3,400만 회 실행되는 Explore 에이전트 규모에서 상당한 토큰 절약이 가능하다.

7.6 에이전트 목록의 어태치먼트 메시지 전환

export function shouldInjectAgentListInMessages(): boolean {
  return getFeatureValue_CACHED_MAY_BE_STALE('tengu_agent_list_attach', false)
}

에이전트 목록이 도구 설명(tool description) 안에 인라인으로 포함되면 MCP 연결, 플러그인 리로드, 권한 모드 변경 시마다 도구 스키마가 바뀌어 전체 프롬프트 캐시가 무효화된다. tengu_agent_list_attach 게이트가 활성화되면 에이전트 목록을 별도의 어태치먼트 메시지로 분리하여 도구 스키마를 정적으로 유지한다. 이는 플릿 전체 cache_creation_tokens의 10.2%를 차지하는 문제를 해결한다.


8. 정리: 오케스트레이션 흐름 요약

완전한 멀티에이전트 작업 흐름은 다음과 같다.

  1. 진입: 사용자 요청이 코디네이터 모드의 메인 에이전트에게 전달된다.
  2. 연구 단계: 메인 에이전트가 여러 AgentTool 호출을 단일 메시지에 포함하여 병렬 워커를 생성한다.
  3. 비동기 실행: 각 워커는 LocalAgentTask로 등록되어 독립적으로 실행된다.
  4. 결과 수신: 워커 완료 시 task-notification XML이 사용자 롤 메시지로 메인 에이전트에게 전달된다.
  5. 종합: 메인 에이전트가 결과를 분석하고 구체적인 구현 명세를 작성한다.
  6. 계속: SendMessageTool(to: agentId)로 기존 워커를 재개하거나 새 워커를 생성한다.
  7. 팀 관리: 필요시 TeamCreateTool로 영속적인 팀 컨텍스트를 생성하고, 작업 완료 후 TeamDeleteTool로 정리한다.

참고 자료

  • src/coordinator/coordinatorMode.ts — 코디네이터 모드 활성화, 시스템 프롬프트, 세션 재개 로직
  • src/tools/AgentTool/AgentTool.tsx — 에이전트 생성, 백그라운드 타임아웃, 출력 스키마
  • src/tools/AgentTool/loadAgentsDir.tsAgentDefinition 타입 계층, 마크다운/JSON 파싱
  • src/tools/AgentTool/forkSubagent.ts — 포크 메커니즘, 캐시 공유, 재귀 방지
  • src/tools/AgentTool/prompt.ts — 에이전트 도구 프롬프트 생성, 어태치먼트 전환
  • src/tools/AgentTool/runAgent.ts — 에이전트 수명주기, MCP 서버, 도구 풀 구성
  • src/tools/SendMessageTool/SendMessageTool.ts — 에이전트 간 메시지 라우팅
  • src/tools/TeamCreateTool/TeamCreateTool.ts — 팀 생성, 태스크 디렉터리 초기화
  • src/tools/TeamDeleteTool/TeamDeleteTool.ts — 팀 정리, 안전 검사
  • src/tasks/LocalAgentTask/LocalAgentTask.tsx — 로컬 비동기 에이전트 태스크 관리
  • src/tasks/RemoteAgentTask/RemoteAgentTask.tsx — 원격 CCR 에이전트 태스크
  • src/tasks/types.tsTaskState 유니언 타입 정의

Navigation