Claude Code Agent Teams 리버스 엔지니어링: 아키텍처와 프로토콜

기계 번역 원문.

Banner for Claude Code Agent Teams 리버스 엔지니어링: 아키텍처와 프로토콜

내용

소개

Claude Code(v2.1.47)에는 Agent Teams라는 실험적 기능이 포함되어 있습니다. 여러 Claude Code 세션이 리드와 팀원으로 구성된 구조를 통해 공동 작업을 조율합니다. 저는 비슷한 목표를 추구하지만 아키텍처는 매우 다른 멀티 에이전트 코딩 오케스트레이터인 Hive를 만들고 있어서, Anthropic의 접근 방식이 내부적으로 어떻게 작동하는지 알고 싶었습니다.

이 글에는 다음 과정을 통해 알아낸 내용을 정리했습니다.

  1. 공식 문서 읽기
  2. 이전 팀 세션이 디스크에 남긴 실제 산출물 살펴보기
  3. Claude에게 Claude Code 바이너리(v2.1.47)를 분석하게 해서 구현 세부 사항 알아내기(하하!)

목차

1. 아키텍처 개요

에이전트 팀은 네 가지 구성 요소로 이루어집니다.

구성 요소 역할
팀 리드 팀을 만들고 팀원을 생성하는 메인 Claude Code 세션
팀원 각각 자체 컨텍스트 윈도우를 가진 별도의 Claude Code 인스턴스
작업 목록 디스크에 개별 JSON 파일로 저장되는 공유 작업 항목
메일박스 메시지 전달에 사용하는 에이전트별 수신함 파일

전체 조율 계층은 파일 기반입니다. ~/.claude/의 파일시스템이 유일한 조율 기반입니다.

~/.claude/
├── teams/{team-name}/
│   ├── config.json                  # team membership registry
│   └── inboxes/{agent-name}.json    # per-agent mailbox
└── tasks/{team-name}/
    ├── .lock                        # flock() for concurrent task claiming
    ├── .highwatermark               # auto-increment counter
    ├── 1.json                       # individual task files
    ├── 2.json
    └── ...

이는 근본적으로 탈중앙화된 설계입니다. 리드는 추가 도구(TeamCreate, TeamDelete, SendMessage)를 가진 또 하나의 Claude 세션일 뿐입니다. 백그라운드 프로세스는 없습니다. 공유 파일에 접근하면서 조율이 이루어집니다.

활성 세션에서 Claude에게 어떤 작업을 수행할 팀을 만들어 달라고 요청한 다음, 다른 창에서 아래 명령을 실행하면 파일시스템이 실시간으로 갱신되는 모습을 볼 수 있습니다.

watch -n 0.5 'tree ~/.claude/teams/ 2>/dev/null; echo "---"; tree ~/.claude/tasks/ 2>/dev/null'

예를 들어 다음 프롬프트를 사용했을 때:

can you spawn an agent team to examine this codebase?
  - have one look for bugs
  - have one look for complexity
  - have one look for good things to call out and play devil's advocate against the other two agents

저는 다음과 같은 결과를 관찰했습니다.

teams
└── code-review
    ├── config.json
    └── inboxes
        ├── bug-hunter.json
        ├── complexity-analyst.json
        ├── devils-advocate.json
        └── team-lead.json

팀 설정

~/.claude/teams/{team-name}/config.json의 팀 설정에는 팀원들이 서로를 찾기 위해 읽는 members 배열이 들어 있습니다.

{
  "members": [
    { "name": "team-lead", "agentId": "abc-123", "agentType": "leader" },
    {
      "name": "researcher",
      "agentId": "def-456",
      "agentType": "general-purpose"
    }
  ]
}

이름이 기본적인 수신 대상 식별 수단입니다(UUID도 있지만 라우팅에는 사용되지 않습니다). 모든 메시징과 작업 할당에는 name 필드를 사용합니다.

2. 공유 작업 목록

파일 형식

각 작업은 ~/.claude/tasks/{team-name}/에 개별 JSON 파일로 저장됩니다. 다음은 이전 세션에서 가져온 실제 예시입니다.

{
  "id": "1",
  "subject": "Hunt for bugs across the codebase",
  "description": "...",
  "activeForm": "Hunting for bugs",
  "owner": "bug-hunter",
  "status": "completed",
  "blocks": [],
  "blockedBy": []
}

작업 스키마:

필드 타입 설명
id string .highwatermark를 통해 자동 증가하는 숫자 ID
subject string 명령형 제목(예: "테스트 실행")
description string 상세 요구 사항과 인수 기준
activeForm string 스피너에 표시할 현재진행형 문구("테스트 실행 중")
status string pending → in_progress → completed (또는 deleted)
blocks string[] 이 작업에 의해 차단되는 작업 ID
blockedBy string[] 이 작업을 시작하기 전에 완료되어야 하는 작업 ID

동시성 제어

두 개의 특수 파일이 조율을 담당합니다.

  • .lock: 파일시스템 수준의 상호 배제(flock())에 사용하는 0바이트 파일입니다. 제 컴퓨터에서 관찰한 42개 작업 디렉터리 모두에 있었습니다.
  • .highwatermark: 정수 하나(예: "3", "13")가 들어 있습니다. 자동 증가에 사용할 다음 작업 ID입니다.

작업 선점

작업 선점에는 파일 잠금을 사용해 경쟁 상태를 방지합니다. 팀원들은 ID가 가장 낮은 작업부터 선점하는 순서를 선호합니다. blockedBy 배열이 비어 있지 않은 작업은 해당 작업을 차단하는 모든 작업이 종료 상태가 될 때까지 선점할 수 없습니다.

관찰: 대부분의 작업 디렉터리는 비어 있음

제 컴퓨터에 있는 작업 디렉터리 42개 중 실제 작업 JSON 파일이 들어 있는 것은 5개뿐이었습니다. 나머지 37개에는 .lock와 .highwatermark만 있었습니다. 이는 작업 완료 후 파일이 정리되었거나, Claude가 내부 작업 목록(작업 목록 기능 출시 이후 제공됨)을 사용하면서 하위 작업 파일로 나누지는 않았던 세션이었기 때문일 가능성이 큽니다.

3. 에이전트 간 통신

메일박스 패턴

각 에이전트에는 ~/.claude/teams/{team-name}/inboxes/{agent-name}.json에 JSON 배열 파일이 있습니다. 다음은 팀 리드가 controlplane-agent에게 작업을 배정했던 이전 세션의 실제 수신함입니다.

[
  {
    "from": "team-lead",
    "text": "{\"type\":\"task_assignment\",\"taskId\":\"1\",\"subject\":\"Phase 2: Control-plane - remove participants/presence\",\"description\":\"Remove multiplayer code from the control-plane package...\",\"assignedBy\":\"team-lead\",\"timestamp\":\"2026-02-18T02:37:16.890Z\"}",
    "timestamp": "2026-02-18T02:37:16.890Z",
    "read": false
  }
]

JSON 안의 JSON 인코딩에 주목하세요. text 필드는 직렬화된 메시지 객체가 담긴 JSON 문자열입니다. 바깥쪽 메시지 봉투에는 from, text, timestamp, read 필드가 있습니다.

메시지 유형

type 필드는 text 페이로드 안에 있으며, 다음 유형을 지원합니다.

유형 방향 용도
task_assignment 리드 → 팀원 전체 세부 사항과 함께 작업 할당
message 임의의 에이전트 → 임의의 에이전트 한 수신자에게 직접 메시지 전송
broadcast 리드 → 전체 모든 팀원에게 같은 메시지 전송
shutdown_request 리드 → 팀원 정상 종료 요청
shutdown_response 팀원 → 리드 종료 승인 또는 거부
plan_approval_request 팀원 → 리드 검토할 계획 제출
plan_approval_response 리드 → 팀원 피드백과 함께 승인 또는 거부
idle_notification 팀원 → 리드 팀원의 턴이 끝나면 자동 전송

전달 메커니즘

쓰기 경로: 발신자가 수신자의 수신함 JSON 배열 파일에 새 항목을 추가합니다.

읽기 경로: 수신자가 자신의 수신함 파일을 폴링합니다. 새 메시지는 합성된 대화 턴으로 삽입됩니다(사용자가 보낸 것처럼 표시됩니다).

브로드캐스트: 말 그대로 모든 팀원의 수신함 파일에 같은 메시지를 씁니다. 토큰 비용은 팀 규모에 비례해 선형으로 증가합니다.

통신은 그저 파일에 추가하고 파일을 읽는 방식입니다. 전송부터 수신까지 걸리는 시간은 수신자의 폴링 간격에 따라 달라집니다.

팀원 간 DM 가시성

팀원이 다른 팀원에게 DM을 보내면 리드가 받는 유휴 상태 알림에 간단한 요약이 포함됩니다. 덕분에 리드는 전체 메시지 내용을 보지 않고도 팀원 간 협업 상황을 파악할 수 있습니다.

4. 에이전트 생성과 수명 주기

팀원이 생성되는 방식

각 팀원은 별도의 claude CLI 프로세스입니다. 리드는 Task 도구에 team_name 및 name 매개변수를 지정하여 팀원을 생성합니다. 생성된 프로세스에는 환경 변수가 설정됩니다.

  • CLAUDE_CODE_TEAM_NAME: 생성된 팀원에 자동으로 설정됨
  • CLAUDE_CODE_PLAN_MODE_REQUIRED: 계획 승인이 필요한 경우 true로 설정됨

컨텍스트 초기화

팀원은 다른 새 세션과 동일한 프로젝트 컨텍스트를 불러옵니다.

  • 작업 디렉터리의 CLAUDE.md 파일
  • MCP 서버
  • 스킬
  • 리드가 제공한 생성 프롬프트

리드의 대화 기록은 전달되지 않습니다. 각 팀원은 생성 프롬프트만 컨텍스트로 삼아 새로 시작합니다.

내부 구현

Claude Code v2.1.47의 바이너리를 분석한 결과, 팀원 컨텍스트는 다음 필드를 가진 AsyncLocalStorage를 통해 관리됩니다.

  • agentId, agentName, teamName
  • parentSessionId, color
  • planModeRequired

주요 내부 함수:

  • isTeammate() / isTeamLead(): 역할 감지
  • waitForTeammatesToBecomeIdle(): 리드를 위한 동기화 프리미티브
  • getTeammateContext() / setDynamicTeamContext(): 런타임 컨텍스트 관리

유휴 상태 감지

LLM 턴이 끝날 때마다 팀원은 자동으로 유휴 상태가 되어 리드에게 idle_notification를 보냅니다. 이는 오류나 정체 상태가 아닌 정상적인 대기 상태입니다. 유휴 상태인 팀원에게 메시지를 보내면 다시 깨어납니다(다음 폴링 주기에 수신함의 메시지를 읽습니다).

종료 프로토콜

  1. 리드가 팀원에게 shutdown_request를 보냅니다
  2. 팀원은 승인하거나(정상 종료), 거부할 수 있습니다(이유를 설명하고 작업을 계속함)
  3. TeamDelete를 통해 팀을 정리하면 ~/.claude/teams/{team-name}/와 ~/.claude/tasks/{team-name}/가 삭제됩니다
  4. 아직 활성 상태인 팀원이 있으면 정리에 실패하므로, 먼저 해당 팀원을 종료해야 합니다

권한 상속

팀원은 생성 시점에 리드의 권한 모드를 상속받습니다. 리드가 --dangerously-skip-permissions로 실행되면 모든 팀원도 같은 방식으로 실행됩니다. 생성 후에는 개별 모드를 변경할 수 있지만, 생성 시점에 팀원별로 설정할 수는 없습니다.

5. 품질 게이트와 훅

Agent Teams는 품질 기준을 강제하기 위해 Claude Code의 훅 시스템과 통합됩니다.

TeammateIdle 훅

팀원이 유휴 상태로 전환되기 직전에 실행됩니다. 종료 코드 2를 반환하면 stderr가 피드백으로 전달되고 유휴 상태 전환이 차단되어 팀원이 계속 작업합니다.

{
  "hook_event_name": "TeammateIdle",
  "teammate_name": "researcher",
  "team_name": "my-project"
}

TaskCompleted 훅

작업을 완료로 표시하려 할 때 실행됩니다. 종료 코드 2를 반환하면 완료 처리가 차단되고 stderr가 피드백으로 전달됩니다.

{
  "hook_event_name": "TaskCompleted",
  "task_id": "task-001",
  "task_subject": "Implement user authentication",
  "task_description": "Add login and signup endpoints",
  "teammate_name": "implementer",
  "team_name": "my-project"
}

이 훅은 두 가지 상황에서 실행됩니다. (1) 에이전트가 TaskUpdate를 통해 작업을 명시적으로 완료 처리할 때, 또는 (2) 에이전트 팀의 팀원이 진행 중인 작업이 있는 상태에서 턴을 마칠 때입니다.

훅 핸들러 유형

유형 설명
command 셸 스크립트. stdin으로 JSON을 받고, 종료 코드로 결정을 전달합니다.
prompt 단일 턴 LLM 평가. {ok, reason}를 반환합니다.
agent 읽기 도구를 사용하는 멀티 턴 하위 에이전트. 최대 50턴.

6. 토큰 비용

에이전트 팀은 팀원이 계획 모드로 실행될 때 일반 세션보다 약 7배 더 많은 토큰을 사용합니다. 각 팀원은 별도의 Claude 인스턴스로서 자체적인 전체 컨텍스트 윈도우를 유지합니다.

기준 사용량

  • 평균 Claude Code 사용 비용: 개발자 1인당 하루 약 $6
  • 에이전트 팀: 기본 사용량에 더해 팀 규모에 대략 비례

7. 아키텍처 요약

측면 Claude Code Agent Teams
조율 기반 플랫 파일(~/.claude/tasks/, ~/.claude/teams/)
작업 형식 작업마다 JSON 파일 하나 + 선점에 사용하는 .lock
메시징 JSON 수신함 파일(추가 + 폴링)
에이전트 수명 주기 자체 관리형 CLI 프로세스
작업 격리 공유 작업 디렉터리
병합 전략 없음(에이전트가 파일을 직접 편집)
재시도/에스컬레이션 수동(리드가 결정하거나 사용자가 개입)
토폴로지 리드 + 수평적인 팀원 구조, 팀원 간 메시징
스케줄링 자체 선점(팀원이 다음 작업을 가져감)
상태 영속성 파일만 사용, 프로세스 내 팀원 재개 미지원
품질 게이트 셸 훅(TeammateIdle, TaskCompleted)
토큰 추적 세션별 추적만 가능, 에이전트 간 집계 없음
작업 정체 감지 수동(사용자가 팀원의 작업 중단을 알아챔)
동시성 제어 암묵적(팀 규모 = 팀원 수)
의존성 모델 작업 파일의 blocks/blockedBy

출처

공식 문서

디스크에 남은 산출물(Claude Code v2.1.47)

/Users/tau/.claude/에서 관찰한 항목:

  • config.json 및 inboxes/{agent-name}.json 파일이 있는 팀 디렉터리
  • .lock, .highwatermark, 개별 작업 JSON 파일이 있는 작업 디렉터리
  • team-lead에서 cp-agent로 보낸, 타임스탬프가 2026-02-18T02:37:16.890Z인 작업 할당 메시지 예시

바이너리 분석

Claude Code 바이너리 v2.1.47. 문자열 분석으로 식별한 내부 함수: getTeamName, getAgentName, getAgentId, isTeammate, isTeamLead, waitForTeammatesToBecomeIdle, getTeammateContext, setDynamicTeamContext, createTeammateContext. AsyncLocalStorage 컨텍스트 필드: agentId, agentName, teamName, parentSessionId, color, planModeRequired.

Hive 코드베이스