본문 바로가기
AI/AI 도구

Claude Code 내부 구조 완전 해부: Agent, Team, Skill 시스템의 비밀

by RevFactory 2026. 4. 3.
반응형

2026년 3월 31일, Anthropic의 Claude Code CLI 전체 소스코드가 npm 레지스트리의 .map 파일을 통해 유출되었습니다. 약 1,900개 파일, 51만 줄이 넘는 TypeScript 코드 안에는 AI 에이전트가 어떻게 협업하고, 작업을 나누고, 스킬을 실행하는지에 대한 모든 비밀이 담겨 있었습니다.

이 글에서는 유출된 소스코드를 직접 분석하여, Claude Code의 Agent/Team/Skill 시스템이 실제로 어떻게 구현되어 있는지를 낱낱이 살펴보겠습니다.


들어가며: 세 가지 질문으로 이해하는 Claude Code

Claude Code의 다중 에이전트 시스템을 이해하려면, 먼저 세 가지 핵심 질문을 떠올려 보시면 좋습니다.

  • "누가 작업하는가?" — 이것이 Agent 시스템입니다
  • "무엇을 작업하는가?" — 이것이 Task 시스템입니다
  • "어떻게 작업하는가?" — 이것이 Skill 시스템입니다

 

이 세 시스템은 각각 독립적으로 설계되어 있지만, 실행 시점에 서로 정교하게 맞물려 돌아갑니다. 마치 잘 짜인 오케스트라에서 지휘자(Agent), 악보(Task), 연주 기법(Skill)이 함께 작동하는 것과 같습니다.

그럼 하나씩 들여다보겠습니다.


Part 1. Agent 시스템 — "누가 일하는가"

모든 것의 시작, AgentTool

Claude Code에서 에이전트를 만들고 실행하는 모든 과정은 AgentTool이라는 하나의 도구에서 시작됩니다. 사용자가 "이 작업을 에이전트에게 맡겨줘"라고 요청하면, 내부적으로 이 도구가 호출되는 것이죠.

AgentTool이 받는 입력은 생각보다 단순합니다. 핵심적인 것만 보면:

  • prompt — 에이전트에게 전달할 작업 지시
  • subagent_type — 어떤 종류의 에이전트를 쓸 것인지 (예: "general-purpose", "Explore")
  • model — 어떤 모델을 사용할 것인지 ("sonnet", "opus", "haiku")
  • run_in_background — 백그라운드에서 실행할 것인지

여기까지는 직관적입니다. 그런데 흥미로운 것은, 이 하나의 도구 뒤에 세 가지 완전히 다른 실행 경로가 숨어 있다는 점입니다.

세 갈래의 길: 서브에이전트, Fork, 팀

소스코드를 분석해 보니, AgentTool은 입력 파라미터의 조합에 따라 세 가지 전혀 다른 방식으로 에이전트를 실행합니다.

첫 번째 길: 서브에이전트 (가장 기본적인 방식)

subagent_type을 명시하면 이 경로를 탑니다. "general-purpose 에이전트를 하나 만들어줘"라고 하면 이 방식이죠. 에이전트는 독립된 컨텍스트에서 실행되고, 작업이 끝나면 결과만 돌려줍니다. 가볍고 빠릅니다.

두 번째 길: Fork 서브에이전트 (부모의 기억을 물려받는 방식)

subagent_type을 생략하면, 흥미로운 일이 벌어집니다. FORK_SUBAGENT라는 피처 플래그가 켜져 있을 경우, 에이전트가 부모의 전체 대화 맥락을 그대로 물려받아 실행됩니다. 마치 대화 중간에 분신을 만들어 별도 작업을 시키는 것과 같습니다.

왜 이런 구조가 필요할까요? 소스코드에서 답을 찾을 수 있었습니다. buildForkedMessages라는 함수가 바이트 단위로 동일한 API 요청 접두사를 만들어내는데, 이는 프롬프트 캐시를 공유하기 위한 최적화입니다. 부모가 이미 보낸 긴 대화를 자식이 다시 보낼 필요 없이, 캐시된 결과를 재활용하는 것이죠. Anthropic이 토큰 비용을 얼마나 신경 쓰고 있는지가 드러나는 부분입니다.

재미있는 안전장치도 있습니다. Fork 자식의 대화 기록에 <fork-boilerplate>라는 태그가 삽입되는데, 이 태그가 있으면 Fork 자식이 다시 Fork를 만드는 것이 차단됩니다. 무한 분신 방지 장치인 셈입니다.

세 번째 길: 팀/Swarm (진짜 협업)

name과 team_name 파라미터를 함께 지정하면, 에이전트는 독립 실행이 아니라 팀의 일원으로 생성됩니다. 이 경로에서 에이전트는 메일박스를 통해 서로 메시지를 주고받고, 공유 작업 목록에서 할 일을 가져가며, 실시간으로 협업합니다.

이 세 가지 경로는 서로 상호배타적인 부분도 있습니다. 특히 Fork 방식과 Coordinator 모드는 동시에 활성화될 수 없도록 설계되어 있습니다. 소스코드에서 if (isCoordinatorMode()) return false라는 한 줄이 이 관계를 명확히 보여줍니다.

빌트인 에이전트들의 정체

Claude Code에는 6가지 빌트인 에이전트가 하드코딩되어 있습니다. 각각의 성격이 꽤 다릅니다.

  • general-purpose — 모든 도구를 사용할 수 있는 범용 에이전트. 기본 폴백으로 사용됩니다
  • Explore — haiku 모델을 사용하는 읽기 전용 에이전트. 코드를 편집할 수 없고, CLAUDE.md도 로드하지 않습니다. 주당 5~15 기가토큰을 절약하기 위한 설계라는 점이 인상적입니다
  • Plan — Explore와 비슷하지만, 아키텍처 설계와 계획 수립에 특화되어 있습니다
  • verification — 백그라운드에서 돌아가는 검증 에이전트. 빨간색으로 표시됩니다
  • statusline-setup — sonnet 모델을 사용하며 Read와 Edit만 가능한, 상태줄 설정 전용 에이전트
  • claude-code-guide — haiku 모델에 permissionMode: 'dontAsk'가 설정된 문서 가이드 에이전트. 사용자에게 권한을 묻지 않고 바로 실행됩니다

이 목록에서 알 수 있는 것은, Anthropic이 에이전트마다 최소 권한 원칙을 매우 엄격하게 적용하고 있다는 점입니다. 문서 안내용 에이전트에 파일 편집 권한을 줄 이유가 없고, 코드 탐색용 에이전트에 쓰기 권한을 줄 이유도 없는 것이죠.

에이전트는 어디서 정의되나?

에이전트 정의는 여러 소스에서 로드되며, 우선순위가 있습니다.

built-in → plugin → ~/.claude/agents/ → .claude/agents/ → 피처 플래그 → 관리 정책

나중 소스가 이전 소스를 덮어쓰는 구조입니다. 즉, 프로젝트 디렉토리의 .claude/agents/에 에이전트를 정의하면 빌트인 에이전트를 오버라이드할 수 있습니다. 이는 사용자가 자신만의 전문 에이전트를 만들어 기본 동작을 커스터마이즈할 수 있게 해주는 설계입니다.

에이전트의 기억: 메모리 시스템

에이전트에도 기억이 있습니다. 세 가지 스코프로 나뉘어져 있습니다.

  • user 스코프 (~/.claude/agent-memory/) — 사용자 전역 기억
  • project 스코프 (.claude/agent-memory/) — 프로젝트별 기억
  • local 스코프 (.claude/agent-memory-local/) — 로컬 전용 기억

프로젝트 스코프의 메모리를 로컬로 동기화하는 스냅샷 메커니즘도 구현되어 있습니다. 새 팀원이 합류했을 때, 프로젝트 수준의 기억을 자동으로 받아올 수 있는 구조입니다.


Part 2. Team 시스템 — "함께 일하기"

TeamCreate: 팀을 만드는 순간 벌어지는 일

TeamCreate 도구를 호출하면, 겉으로는 단순히 "팀을 만들었습니다"라는 결과만 보입니다. 하지만 내부에서는 꽤 많은 일이 일어납니다.

  1. 중복 검사 — 현재 세션에 이미 리더로 있는 팀이 있는지 확인합니다. 세션당 한 팀만 리더가 될 수 있습니다
  2. 팀 설정 파일 생성  ~/.claude/teams/{팀이름}/config.json에 팀 구성 정보를 기록합니다
  3. 작업 디렉토리 생성  ~/.claude/tasks/{팀이름}/에 공유 작업 목록 디렉토리를 만듭니다
  4. 세션 정리 등록 — 세션이 끝날 때 팀 리소스를 정리하는 콜백을 등록합니다

여기서 가장 중요한 설계 결정은 Team = TaskList 1:1 대응입니다. 팀을 만드는 것은 곧 공유 작업 큐를 만드는 것이고, 이 큐를 통해 팀원들이 작업을 가져가고 완료 보고를 하게 됩니다. 별도의 메시지 브로커나 데이터베이스 없이, 파일 시스템만으로 분산 작업 관리를 구현한 것입니다.

SendMessage: 메시지 하나에 다섯 가지 길

팀원 간 통신을 담당하는 SendMessage 도구는, 겉보기에는 단순한 메시지 전송이지만 내부적으로 다섯 가지 라우팅 경로를 가지고 있습니다.

  1. Bridge  bridge:session_... 형식으로 보내면, 크로스머신(다른 컴퓨터) 통신이 됩니다. VS Code나 JetBrains IDE와의 통신에 사용됩니다
  2. UDS 소켓  uds:/path/to.sock로 보내면, 같은 기기의 다른 세션으로 전달됩니다
  3. 인프로세스 — 같은 프로세스 안에서 실행 중인 에이전트에게 직접 큐잉합니다
  4. 팀 브로드캐스트  to: "*"로 보내면 전체 팀원에게 발송됩니다
  5. 팀 유니캐스트 — 이름을 지정하면 해당 팀원의 메일박스에 기록됩니다

특히 인상적인 것은 자동 재개 메커니즘입니다. 중지된 에이전트에게 메시지를 보내면, 시스템이 디스크에 저장된 트랜스크립트에서 전체 대화 맥락을 복원하여 에이전트를 자동으로 깨웁니다. "메시지를 보내면 깨어난다"는 직관적인 모델을 코드 수준에서 구현한 것입니다.

SendMessage에는 일반 텍스트 외에 구조화된 프로토콜 메시지도 있습니다.

  • shutdown_request / shutdown_response — 에이전트 종료 요청과 승인
  • plan_approval_response — 계획 승인/거부 (팀 리더만 가능)

종료 승인이 이루어지면 인프로세스 팀원은 abortController.abort()로, 외부 프로세스는 gracefulShutdown()으로 종료됩니다. 꽤 정교한 생명주기 관리입니다.

팀 삭제: 안전장치가 있다

TeamDelete는 빈 입력을 받습니다. 현재 세션의 팀을 자동으로 찾아 삭제하기 때문이죠. 하지만 활성 팀원이 하나라도 있으면 삭제를 거부합니다. "먼저 shutdown을 요청하세요"라는 메시지와 함께요.

이는 실수로 작업 중인 에이전트를 날려버리는 것을 방지하기 위한 안전장치입니다. 팀 운영의 기본 원칙이 코드에 녹아 있는 것이죠.

Coordinator 모드: 지휘자 전용 모드

환경변수 CLAUDE_CODE_COORDINATOR_MODE를 켜면 특별한 모드가 활성화됩니다. 이 모드에서 Claude는 직접 도구를 실행하지 않고, 오로지 워커 에이전트를 관리하는 지휘자가 됩니다.

사용할 수 있는 도구가 Agent, SendMessage, TaskStop으로 극도로 제한되며, 정해진 4단계 워크플로를 따릅니다.

  1. Research — 워커들이 병렬로 코드베이스를 조사합니다
  2. Synthesis — Coordinator가 발견 내용을 종합하고 구현 사양을 작성합니다
  3. Implementation — 워커들이 사양에 따라 구현합니다
  4. Verification — 워커들이 변경 사항을 검증합니다

흥미로운 점은, Coordinator의 "Scratchpad" 기능입니다. tengu_scratch라는 피처 게이트 뒤에 숨어 있는데, 워커들이 발견한 정보를 공유 저장소에 기록하여 다른 워커가 참조할 수 있게 하는 메커니즘입니다.


Part 3. Task 시스템 — "무엇을 해야 하는가"

놀라운 발견: 두 개의 Task 시스템이 공존한다

소스코드를 분석하면서 가장 놀라웠던 발견 중 하나입니다. Claude Code에는 완전히 분리된 두 가지 Task 시스템이 공존하고 있었습니다.

첫 번째: Todo Task (작업 관리 시스템)

이것은 우리가 일반적으로 생각하는 "할 일 목록"입니다. LLM이 TaskCreate, TaskUpdate, TaskList 같은 도구를 통해 관리하는 구조화된 작업 목록이죠.

  • 파일 시스템에 JSON으로 저장됩니다 (~/.claude/tasks/{listId}/{id}.json)
  • 순차 번호 ID를 사용합니다 (1, 2, 3...)
  • 상태는 pending → in_progress → completed입니다
  • 에이전트 팀에서 공유 작업 큐로 기능합니다

두 번째: Runtime Task (실행 엔진)

이것은 실제로 실행 중인 프로세스를 추적하는 시스템입니다. 셸 명령, 에이전트, 원격 세션 등의 생명주기를 관리합니다.

  • React AppState에 인메모리로 저장됩니다
  • 타입 접두어 + 랜덤 8자리 ID를 사용합니다 (예: b4f2a1c9 = bash task)
  • 상태는 pending → running → completed/failed/killed입니다
  • 7가지 타입이 있습니다: local_bash, local_agent, remote_agent, in_process_teammate, local_workflow, monitor_mcp, dream

두 시스템은 직접적으로 연결되지 않습니다. 하지만 Agent 시스템을 매개로 간접적으로 연결됩니다. 예를 들어, AgentTool이 에이전트를 생성하면 LocalAgentTask(Runtime Task)가 등록되고, 동시에 팀 모드에서는 TaskCreate(Todo Task)로 작업이 할당될 수 있습니다.

파일 시스템이 곧 데이터베이스

Todo Task 시스템의 동시성 제어 방식이 흥미롭습니다. Redis나 PostgreSQL 같은 외부 데이터베이스를 사용하지 않고, proper-lockfile 라이브러리로 파일 수준 락을 구현했습니다.

 

락 옵션: retries=30, minTimeout=5ms, maxTimeout=100ms

 

최대 30번 재시도하며, 5~100ms 사이의 백오프로 약 10개 이상의 동시 에이전트를 지원합니다. 또한 High Water Mark 파일을 사용하여 삭제된 작업의 ID가 재사용되는 것을 방지합니다.

별도의 데이터베이스 서버 없이 파일 시스템만으로 분산 작업 관리를 구현한 것은, CLI 도구라는 특성상 외부 의존성을 최소화하려는 설계 철학이 반영된 것으로 보입니다.

팀 전체가 하나의 작업 큐를 공유하는 방법

getTaskListId()라는 함수가 핵심입니다. 이 함수는 현재 에이전트가 속한 팀을 파악하여, 팀의 모든 에이전트가 동일한 taskListId를 반환하도록 합니다.

우선순위는 이렇습니다.

  1. CLAUDE_CODE_TASK_LIST_ID 환경변수
  2. 인프로세스 팀원의 teammateCtx.teamName
  3. CLAUDE_CODE_TEAM_NAME 환경변수
  4. leaderTeamName (TeamCreate로 설정됨)
  5. sessionId (독립 세션의 폴백)

결과적으로, ~/.claude/tasks/{팀이름}/ 디렉토리가 팀 전체의 공유 작업 큐가 됩니다. 팀원이 작업을 완료하면 TaskUpdate로 상태를 변경하고, TaskList를 호출하여 다음 할 일을 찾아가는 구조입니다.

검증 에이전트 넛지: 영리한 품질 관리

TaskUpdateTool에 숨겨진 영리한 기능이 하나 있습니다. 3개 이상의 작업이 모두 완료된 상태에서, 그 중 "verification"이라는 키워드가 포함된 작업이 하나도 없으면, verificationNudgeNeeded=true를 반환합니다.

이 신호를 받은 모델은 검증 에이전트를 스폰하라는 권고를 받게 됩니다. 강제가 아니라 넛지라는 점이 인상적입니다. 모델의 자율성을 존중하면서도 품질 보증 패턴을 유도하는 세련된 방식이죠.

292개 에이전트와 36.8GB의 교훈

소스코드의 주석에서 발견한 실제 사례입니다. InProcessTeammateTask의 타입 정의 파일에 이런 내용이 있었습니다.

"세션 9a990de8가 2분 내 292개 에이전트를 생성하여 36.8GB RSS에 도달"

이 사례 이후, 팀원의 메시지 배열이 50개로 캡핑되는 최적화가 적용되었습니다. 에이전트당 평균 ~20MB의 RSS를 사용하며, 500턴 이상의 긴 대화에서는 ~125MB까지 올라갈 수 있다는 분석도 함께 기록되어 있었습니다.

실전에서 발생한 문제가 코드에 직접적인 제약 조건으로 녹아든 좋은 사례입니다.


Part 4. Skill 시스템 — "어떻게 일하는가"

스킬이란 무엇인가

스킬은 재사용 가능한 워크플로우입니다. /commit, /review-pr 같은 슬래시 커맨드가 바로 스킬이죠. 사용자가 /commit을 입력하면, 시스템은 미리 정의된 커밋 워크플로우 프롬프트를 현재 대화에 주입합니다.

SkillTool의 입력은 단순합니다.

 

{ skill: "commit", args: "-m 'Fix bug'" }

하지만 이 단순한 인터페이스 뒤에는 복잡한 실행 체계가 있습니다.

Inline vs Fork: 스킬의 두 가지 실행 모드

스킬은 두 가지 방식으로 실행됩니다.

Inline 모드 (기본)

스킬의 프롬프트가 현재 대화에 직접 주입됩니다. 마치 시스템 프롬프트에 추가 지시를 붙이는 것과 같습니다. contextModifier를 통해 허용 도구, 모델, 추론 노력 수준을 오버라이드할 수 있습니다.

Fork 모드

스킬의 SKILL.md frontmatter에 context: 'fork'가 설정되어 있으면, 별도의 서브에이전트에서 실행됩니다. 여기서 핵심적인 발견이 있었는데, Fork 스킬은 AgentTool의 runAgent() 함수를 그대로 재사용합니다.

즉, Skill 시스템과 Agent 시스템은 실행 인프라를 공유하는 것입니다. 스킬의 agent frontmatter 필드로 사용할 에이전트 타입을 지정하면, 해당 에이전트 정의의 시스템 프롬프트와 도구 접근 권한이 적용됩니다. "스킬은 무엇을 하는가를 정의하고, 에이전트는 누가 하는가를 정의한다"는 분리 원칙이 코드 수준에서 실현된 것입니다.

8단계 스킬 로딩 파이프라인

스킬이 어디서 로드되는지를 추적해 보면, 놀랍도록 다층적인 구조가 드러납니다.

  1. managed — 조직 정책에 의해 관리되는 스킬
  2. user  ~/.claude/skills/의 사용자 글로벌 스킬
  3. project  .claude/skills/의 프로젝트 스킬
  4. additional  --add-dir 플래그로 지정된 추가 경로
  5. legacy — 이전 버전의 /commands/ 디렉토리 (하위 호환)
  6. bundled — 코드에 내장된 번들 스킬 (commit, review 등)
  7. builtin plugin — 빌트인 플러그인이 제공하는 스킬
  8. MCP — MCP 서버가 제공하는 스킬

각 단계는 병렬로 로딩되며, memoize로 캐싱됩니다. 같은 이름의 스킬이 여러 소스에 있으면 나중 소스가 우선합니다.

Description이 모든 것을 결정한다

스킬의 description 필드가 왜 그토록 중요한지, 소스코드에서 명확히 확인할 수 있었습니다.

시스템 프롬프트에 스킬 목록이 포함될 때, 각 스킬은 이런 형식으로 표시됩니다.

 

- commit: Git 커밋을 생성합니다 - 코드 변경 후 커밋이 필요할 때 사용

 

모델은 이 리스팅을 보고 적절한 스킬을 선택하여 호출합니다. 즉, description이 유일한 트리거 메커니즘인 것입니다.

더 흥미로운 것은 프롬프트 예산 관리입니다. 스킬 리스팅에는 컨텍스트 윈도우의 **딱 1%**만 할당됩니다 (SKILL_BUDGET_CONTEXT_PERCENT = 0.01). 개별 설명은 최대 250자로 제한됩니다.

스킬이 너무 많아서 예산을 초과하면 어떻게 될까요?

  1. 먼저 비번들(사용자 정의) 스킬의 설명을 축소합니다
  2. 그래도 부족하면 비번들 스킬은 이름만 표시합니다
  3. 번들 스킬은 항상 전체 설명을 유지합니다

Anthropic의 공식 스킬이 서드파티 스킬보다 우선순위가 높다는 것이 코드에 명시적으로 드러나는 부분입니다.

조건부 스킬: 필요할 때만 나타나는 스킬

SKILL.md의 frontmatter에 paths 필드를 설정하면, 해당 경로의 파일이 터치될 때만 스킬이 활성화됩니다. 이는 컨텍스트 윈도우 공간을 절약하기 위한 설계입니다.

예를 들어, Docker 관련 스킬에 paths: ["Dockerfile", "docker-compose.yml"]를 설정하면, 이 파일들을 건드릴 때만 스킬이 시스템 프롬프트에 나타납니다. gitignore 패턴 매칭 라이브러리를 사용하여 구현되어 있습니다.

동적 발견도 지원됩니다. 파일을 편집하면 상위 디렉토리를 탐색하여 .claude/skills/ 디렉토리를 자동으로 찾아내고, 거기서 스킬을 로딩합니다. 단, node_modules/ 같은 gitignore된 디렉토리는 건너뜁니다.

보안: MCP 스킬은 특별 취급

MCP(Model Context Protocol) 서버가 제공하는 스킬에는 중요한 보안 제한이 있습니다. 인라인 셸 커맨드 실행이 차단됩니다.

일반 스킬의 프롬프트에서는 !`echo hello` 같은 구문으로 셸 명령을 실행할 수 있지만, MCP 스킬은 원격/비신뢰 소스이므로 이 기능이 완전히 비활성화됩니다. 소스코드 주석에 "MCP 스킬은 원격/비신뢰이므로"라고 명시되어 있습니다.

번들 스킬의 참조 파일 추출에도 보안 장치가 있습니다.

  • O_NOFOLLOW, O_EXCL 플래그로 심볼릭 링크 공격 방지
  • 프로세스별 nonce 디렉토리에 추출하여 충돌 방지
  • .. 포함 경로를 거부하여 경로 순회 공격 차단
  • 0o700 (디렉토리), 0o600 (파일) 권한으로 소유자 전용 접근

Part 5. 세 시스템은 어떻게 연결되는가

Agent-Task 연동: 가장 깊은 연결

Agent 시스템과 Task 시스템의 연동은 여러 층위에서 일어납니다.

생성 시점:

  • TeamCreate가 호출되면 setLeaderTeamName()이 실행되어, Todo Task의 공유 리스트가 활성화됩니다
  • AgentTool이 에이전트를 생성하면 LocalAgentTask가 Runtime Task로 등록됩니다.

실행 중:

  • TaskUpdate로 owner를 변경하면 writeToMailbox()로 새 소유자에게 알림이 갑니다
  • SendMessage로 전달된 메시지는 queuePendingMessage()로 큐잉되고, 도구 실행 라운드 경계에서 배출됩니다

종료 시점:

  • 에이전트가 종료되면 killShellTasksForAgent()가 고아 bash 프로세스를 정리합니다
  • unassignTeammateTasks()가 종료된 팀원의 미완료 작업을 pending으로 되돌립니다

Skill-Agent 연동: 인프라 공유

Fork 스킬이 runAgent()를 재사용한다는 것은 앞서 말씀드렸습니다. 이 외에도

  • 에이전트 정의에 skills?: string[] 필드가 있어, 에이전트가 사용할 스킬을 명시할 수 있습니다
  • Coordinator 프롬프트에는 "delegate skill invocations to workers"라는 지시가 포함되어 있습니다
  • 에이전트 종료 시 clearInvokedSkillsForAgent()로 스킬 상태가 정리됩니다

Task-Skill 간접 연동

SkillTool 자체는 TaskTool과 직접 상호작용하지 않습니다. 하지만 Fork 스킬이 서브에이전트로 실행될 때, 그 에이전트는 TaskTool을 사용할 수 있습니다. 스킬의 allowed-tools frontmatter로 이 접근을 제어할 수 있고요.

결국 세 시스템은 "느슨한 결합, 긴밀한 협력"이라는 원칙 위에 설계되어 있습니다.


Part 6. 올바른 사용법 — 소스코드가 말하는 베스트 프랙티스

소스코드 분석에서 도출된, 시스템이 의도한 사용법을 정리해 보겠습니다.

Agent 생성의 원칙

  • 모델은 목적에 맞게 선택하세요. 빌트인 에이전트를 보면, 단순 탐색에는 haiku를, 범용에는 기본 모델을, 정밀 작업에는 opus를 사용합니다
  • 도구 접근은 최소한으로. Explore 에이전트는 읽기 전용, statusline-setup은 Read/Edit만 허용합니다. 불필요한 도구 접근은 제거하세요
  • 이름을 지정하면 통신이 가능합니다. name 파라미터를 주면 SendMessage로 주소 지정이 가능해지지만, UUID가 아닌 이름으로만 통신해야 합니다

Team 운영의 원칙

  • 세션당 한 팀. 팀을 해체하고 새로 만들 수는 있지만, 동시에 두 팀의 리더가 될 수는 없습니다
  • 팀 삭제 전 반드시 shutdown. 활성 멤버가 있으면 삭제가 거부됩니다
  • 브로드캐스트는 비용이 큽니다. to: "*"는 팀원 수에 선형 비례하는 비용이 발생하므로, 꼭 필요할 때만 사용하세요

Task 관리의 원칙

  • 완료 후 반드시 TaskList를 호출하세요. 시스템이 명시적으로 "Call TaskList now to find your next available task"라고 안내합니다
  • ID 순서대로 작업하세요. 낮은 ID의 작업이 높은 ID의 작업에 컨텍스트를 제공하는 경우가 많습니다
  • 3개 이상 완료했으면 검증을 고려하세요. 시스템이 넛지를 보내는 시점이 바로 이때입니다

Skill 작성의 원칙

  • description을 적극적으로 쓰세요. 이것이 유일한 트리거 메커니즘입니다. 250자 안에 스킬이 하는 일과 트리거 상황을 모두 담아야 합니다
  • 프롬프트 예산을 의식하세요. 컨텍스트 윈도우의 1%만 스킬 리스팅에 할당됩니다
  • 조건부 활성화를 활용하세요. paths frontmatter로 관련 파일이 터치될 때만 스킬이 나타나게 하면, 컨텍스트 공간을 절약할 수 있습니다
  • Fork가 필요하면 context: 'fork'를 쓰세요. 독립된 컨텍스트가 필요하거나 긴 작업이라면 Fork 모드가 적합합니다

Part 7. 설계 패턴과 교훈

파일 시스템을 데이터베이스로

Claude Code는 외부 데이터베이스 없이 파일 시스템만으로 분산 작업 관리, 메일박스 통신, 에이전트 메모리를 구현했습니다. CLI 도구라는 특성상 "npm install 하나로 끝나야 한다"는 제약 조건 아래에서의 영리한 선택입니다.

캐시 최적화에 대한 집착

Fork 서브에이전트의 바이트 동일 접두사, Explore 에이전트의 CLAUDE.md 제거 (주당 5~15 기가토큰 절감), 스킬 리스팅 예산 관리 — 곳곳에서 토큰 비용 최적화에 대한 집착이 느껴집니다. API 비용이 곧 사용자 비용인 제품에서는 당연한 우선순위일 것입니다.

넛지 > 강제

검증 에이전트 넛지, 작업 완료 후 TaskList 호출 안내 등, 시스템은 모델에게 직접 명령하기보다는 올바른 방향으로 유도하는 방식을 선호합니다. LLM의 자율성을 존중하면서도 품질을 보장하려는 균형점입니다.

실전에서 배운 제약 조건

292개 에이전트 / 36.8GB RSS 사례에서 50개 메시지 캡핑이 나왔고, 에이전트 종료 시 고아 프로세스 정리 로직이 "10일짜리 fake-logs.sh 좀비 방지"라는 주석과 함께 추가되었습니다. 이론적 설계가 아니라, 실전에서 부딪힌 문제가 코드에 직접 반영된 것입니다.

보안의 다층 방어

도구 필터링 → Transcript Classifier → Permission Mode → Safe Properties → 파일 추출 보안까지, 보안이 단일 지점이 아니라 여러 층위에 걸쳐 있습니다. 하나의 방어가 뚫려도 다음 층에서 잡을 수 있는 구조입니다.


마치며

Claude Code의 소스코드를 분석하면서 느낀 것은, 이 시스템이 단순한 "AI 에이전트 프레임워크"가 아니라 실전에서 단련된 프로덕션 시스템이라는 점입니다.

세 가지 에이전트 실행 방식의 공존, 파일 시스템 기반의 분산 작업 관리, 토큰 비용에 대한 집착적 최적화, 292개 에이전트 사고에서 배운 메모리 제한 — 이 모든 것이 이론이 아닌 실전의 산물입니다.

특히 Agent(누가), Task(무엇을), Skill(어떻게)이라는 세 축의 분리와 연동 설계는, AI 에이전트 시스템을 구축하려는 분들에게 훌륭한 참고 자료가 될 것입니다.

유출이라는 경로로 공개된 것이 아쉽지만, 이 코드가 보여주는 설계 철학과 실전 교훈은 AI 엔지니어링 커뮤니티에 값진 자산이 될 것이라 생각합니다.

 

반응형