Claude Code 의 코드 (1)
에이전트 하네스 : Claude Code는 어떻게 동작하는가
들어가며
2026년 3월 31일 새벽 4시, Claude Code의 소스코드가 유출되었다. Anthropic의 AI 코딩 에이전트의 내부가 그대로 드러났다.
이를 누군가 Python으로 클린룸 재작성해서 GitHub에 올렸고, 그 레포가 몇 시간 만에 3만 스타를 넘겼다. 유출된 원본 코드 자체는 법적, 윤리적 문제가 있지만, 그 구조를 분석해서 재작성한 코드는 공개되어 있다.
이 글은 그 재작성된 코드를 보고 Claude Code가 내부적으로 어떤 구조를 가지고 있는지 뜯어보는 글이다. 원본 TypeScript 1902개 파일의 아키텍처를 분석하고, “AI 에이전트가 실제로 어떻게 동작하는가”에 대한 감을 잡는 것이 목표다.
에이전트 하네스란 무엇인가
에이전트 하네스(Agent Harness) 라는 단어가 익숙하지 않을 수 있다.
하네스(Harness) 는 원래 마구(馬具)를 뜻한다. 말에 고삐를 채워서, 말의 힘을 사람이 원하는 방향으로 쓸 수 있게 해주는 장치다. 말이 아무리 빨라도 고삐 없이는 마차를 끌 수 없다.
AI 에이전트도 마찬가지다. Claude 같은 LLM은 엄청난 능력을 가지고 있지만, 그 자체로는 파일을 읽지도, 코드를 실행하지도, 터미널 명령을 내리지도 못한다. LLM은 본질적으로 텍스트를 입력받아 텍스트를 출력하는 함수 일 뿐이다.
에이전트 하네스는 이 LLM에 고삐를 채우는 시스템이다. 구체적으로 다섯 가지를 책임진다.
- 사용자의 요청을 받아서 LLM이 이해할 수 있는 형태로 정리한다
- LLM이 “파일을 읽어야 할 것 같다”고 판단하면, 실제로 파일을 읽어주는 도구를 실행한다
- 위험한 명령(예:
rm -rf /)이 요청되면 실행 전에 차단하거나 사용자에게 확인을 받는다 - LLM과 도구 사이의 대화를 턴 단위로 관리하며, 토큰 예산이 초과되지 않도록 조절한다
- 세션 기록을 저장하고, 이전 대화를 이어갈 수 있게 한다
이 다섯 가지가 없으면 LLM은 그냥 채팅봇이다. 이 다섯 가지가 있으면 LLM은 코딩 에이전트가 된다. 그 차이를 만드는 게 에이전트 하네스다.
조금 더 직관적으로 이해하기 위해 식당에 비유해보자.
LLM은 셰프 다. 어떤 요리든 만들 수 있는 실력이 있다. 하지만 셰프 혼자서는 식당을 운영할 수 없다. 주문을 받아야 하고, 재료를 꺼내야 하고, 화재를 방지해야 하고, 영수증도 써야 한다.
에이전트 하네스는 주방 시스템 전체다.
- 주문 접수(부트스트랩) → 식당이 문을 열 때 가스 밸브 확인하고, 냉장고 상태 점검하고, 직원 출근 확인하는 과정. 시스템이 켜질 때 환경을 검사하고, 도구를 로딩하고, 신뢰 여부를 판단하는 과정이다
- 도구 선반(커맨드와 툴) → 칼, 프라이팬, 오븐이 정리되어 있듯이 파일 읽기, 코드 실행, 웹 검색 같은 도구가 체계적으로 분류되어 있다. Claude Code에는 이런 도구가 184개 있다
- 레시피 라우팅(런타임) → 파스타 주문이 들어오면 파스타 도구를 꺼내고, 스테이크 주문이면 그릴을 준비한다. “이 버그 고쳐줘”라고 하면
FileReadTool과FileEditTool을 꺼내고, “이 코드 리뷰해줘”라고 하면review커맨드를 실행한다 - 안전 관리(퍼미션) → 신입 요리사에게 가스레인지를 함부로 쓰게 하지 않듯이, 위험한 셸 명령은 권한을 확인한 뒤에만 실행한다.
BashTool하나에만 보안 관련 파일이 6개 붙어 있다 - 주문 기록(세션 관리) → 오늘 뭘 만들었는지, 재료를 얼마나 썼는지 기록한다. 토큰 사용량을 추적하고, 대화가 너무 길어지면 이전 내용을 요약해서 압축한다
Claude Code의 1902개 파일은 바로 이 주방 시스템을 구성하는 부품들이다.
유출 코드베이스의 규모
claw-code 레포에 남아 있는 스냅샷 데이터를 기반으로, 원본 Claude Code TypeScript 코드베이스의 규모를 정리하면 이렇다.
| 항목 | 수량 |
|---|---|
| TypeScript/TSX/JS 파일 | 1902개 |
| 커맨드 모듈 | 207개 |
| 툴 모듈 | 184개 |
| 서브시스템 디렉토리 | 35개 |
| 루트 레벨 파일 | 18개 |
1902개 파일이 시사하는 바는 Claude Code가 단순히 “AI한테 명령 내리는 터미널 도구”가 아니라, 대규모 소프트웨어 시스템이라는 걸 말한다.
35개의 서브시스템
서브시스템의 이름과 규모를 보면, 이 시스템이 얼마나 많은 영역을 커버하는지 감이 온다. 전부 다 볼 필요는 없고, 큰 덩어리 위주로 묶어서 보자.
핵심 런타임
| 서브시스템 | 모듈 수 | 역할 |
|---|---|---|
| utils | 564개 | 유틸리티 함수 모음. 전체의 약 30%를 차지한다 |
| components | 389개 | UI 컴포넌트. Ink(React 기반 터미널 UI) 렌더링 |
| services | 130개 | API 통신, 분석, 세션 메모리 등 핵심 서비스 |
| hooks | 104개 | React 훅. 퍼미션 핸들러, 알림, 상태 관리 |
utils가 564개라는 건 조금 놀랍다. 전체 파일의 약 30%가 유틸리티다. 셸 명령 실행, 에이전트 컨텍스트 관리, 인증, API 프리커넥트 같은 기능이 여기 몰려 있다. 이건 시스템의 기초 뼈대라고 봐야 한다.
components가 389개인 것도 인상깊다. Claude Code는 터미널 도구인데 UI 컴포넌트가 389개나 있다. 이건 Claude Code가 단순 CLI가 아니라 Ink 기반의 인터랙티브 터미널 UI 를 가지고 있기 때문이다. 자동 업데이트 다이얼로그, 권한 요청 팝업, 컨텍스트 시각화, 에이전트 진행 상태 표시 같은 것들이 전부 React 컴포넌트로 만들어져 있다.
도구와 명령
| 서브시스템 | 모듈 수 | 역할 |
|---|---|---|
| commands | 207개 | 슬래시 커맨드 (/review, /plan, /commit 등) |
| tools | 184개 | 실행 도구 (BashTool, FileEditTool, AgentTool 등) |
| skills | 20개 | 번들 스킬 (배치 처리, 디버그, 루프 등) |
| plugins | 2개 | 플러그인 시스템 기반 |
커맨드 207개와 툴 184개를 보면 “왜 이렇게 많지?”라는 의문이 든다. 몇 가지만 살펴보자.
커맨드 쪽을 보면 review, commit, plan, doctor, vim, voice, bridge, teleport, ultraplan, thinkback 같은 이름들이 보인다. 단순한 CRUD가 아니라, 코드 리뷰, 커밋, 작업 계획, 시스템 진단, Vim 모드, 음성 입력, 원격 연결, 사고 과정 재생 같은 고수준 기능들이다.
툴 쪽은 더 신기한데, BashTool 하나에만 bashSecurity.ts, bashPermissions.ts, destructiveCommandWarning.ts, sedValidation.ts, pathValidation.ts, shouldUseSandbox.ts 같은 파일이 붙어 있다. 셸 명령 하나 실행하는 데 보안 파일이 6개다. “왜 이렇게까지?”라는 질문에 대한 답은 나중에 보겠지만 쉽게 말하면 AI가 rm -rf /를 실행하면 안 되기 때문 이다.
AgentTool도 보면 forkSubagent.ts, runAgent.ts, resumeAgent.ts, agentMemory.ts 같은 파일이 있다. 에이전트가 다른 에이전트를 포크하고, 실행하고, 메모리를 공유한다. 멀티 에이전트 시스템이 이미 내장되어 있다는 뜻이다.
인프라 및 커넥션
| 서브시스템 | 모듈 수 | 역할 |
|---|---|---|
| bridge | 31개 | 원격 연결, 세션 공유, JWT 인증 |
| cli | 19개 | CLI 파서, 전송 계층 (SSE, WebSocket) |
| constants | 21개 | 시스템 상수, 프롬프트, API 제한값 |
| keybindings | 14개 | 키바인딩 시스템 (Vim 스타일 지원) |
| migrations | 11개 | 설정 마이그레이션 (모델 변경 대응) |
bridge가 31개 모듈이라는 건, 원격 연결 기능이 정교하다는 뜻이다. bridgeApi.ts, bridgeMessaging.ts, jwtUtils.ts, pollConfig.ts, remoteBridgeCore.ts 같은 파일들이 있다. Claude Code가 단순히 로컬에서만 돌아가는 게 아니라, 원격 세션 공유, SSH 프록시, 텔레포트(세션 이동) 같은 기능을 지원한다는 걸 보여준다.
migrations에는 migrateFennecToOpus.ts, migrateSonnet1mToSonnet45.ts, migrateSonnet45ToSonnet46.ts 같은 파일들이 있는데, 파일명에서도 보이듯 이는 모델이 업그레이드될 때마다 사용자 설정을 자동으로 마이그레이션해주는 시스템이 있다는 뜻이다. 이런 세심한 부분이 프로덕션 소프트웨어와 사이드 프로젝트의 차이인 것 같다.
나머지 서브시스템
| 서브시스템 | 모듈 수 | 역할 |
|---|---|---|
| entrypoints | 8개 | CLI, MCP, SDK 진입점 |
| memdir | 8개 | 메모리 디렉토리 (대화 기억 관리) |
| state | 6개 | 앱 전역 상태 관리 |
| buddy | 6개 | 동반 캐릭터 시스템 (CompanionSprite) |
| vim | 5개 | Vim 모션, 오퍼레이터, 텍스트 오브젝트 |
| remote | 4개 | 원격 세션 매니저, WebSocket |
| native-ts | 4개 | 네이티브 모듈 (파일 인덱스, 레이아웃) |
| types | 11개 | 타입 정의 |
| schemas | 1개 | 훅 스키마 |
| coordinator | 1개 | 코디네이터 모드 |
| assistant | 1개 | 세션 히스토리 |
| bootstrap | 1개 | 부트스트랩 상태 |
| moreright | 1개 | 확장 UI |
| outputStyles | 1개 | 출력 스타일 |
| voice | 1개 | 음성 모드 플래그 |
| server | 3개 | 다이렉트 커넥트 서버 |
| screens | 3개 | Doctor, REPL, Resume 화면 |
| upstreamproxy | 2개 | 업스트림 프록시 릴레이 |
buddy라는 서브시스템이 눈에 띈다. CompanionSprite.tsx, companion.ts, sprites.ts 같은 파일이 있는데 여기 동반 캐릭터 시스템이 내장되어 있다. 프로덕션 코딩 에이전트에 캐릭터 스프라이트가 있다는 게 재미있다.
vim 서브시스템은 motions.ts, operators.ts, textObjects.ts 같은 파일로 구성되어있다. 단순히 Vim 키바인딩 몇 개 매핑한 게 아니라 Vim의 모션/오퍼레이터/텍스트 오브젝트 모델을 제대로 구현한 것으로 보인다.
루트 레벨 파일
서브시스템 35개 외에, 루트 레벨에 18개 파일이 있다. 이 파일들이 시스템의 핵심 중추 역할을 한다.
| 원본 파일 (TypeScript) | 역할 |
|---|---|
| main.tsx | 전체 진입점. CLI 파서, 모드 라우팅, 쿼리 엔진 루프 |
| QueryEngine.ts | 핵심 엔진. 사용자 입력 → 커맨드/툴 라우팅 → 결과 반환 |
| commands.ts | 207개 커맨드의 레지스트리 |
| tools.ts | 184개 툴의 레지스트리 |
| setup.ts | 부트스트랩. 환경 검사, prefetch, trust gate |
| context.ts | 워크스페이스 컨텍스트 (파일 수, 프로젝트 구조) |
| history.ts | 세션 히스토리 관리 |
| cost-tracker.ts | 토큰 비용 추적 |
| costHook.ts | 비용 훅 (비용 이벤트 기록) |
| Task.ts | 태스크 단위 계획 구조 |
| tasks.ts | 태스크 목록 관리 |
| Tool.ts | 툴 정의 인터페이스 |
| query.ts | 쿼리 요청/응답 데이터 구조 |
| ink.ts | 터미널 UI 렌더링 (Ink 프레임워크) |
| interactiveHelpers.tsx | 인터랙티브 UI 헬퍼 |
| dialogLaunchers.tsx | 다이얼로그 런처 |
| projectOnboardingState.ts | 프로젝트 온보딩 상태 |
| replLauncher.tsx | REPL 런처 |
이 18개 파일을 보면 시스템의 전체 흐름을 볼 수 있다. main.tsx에서 시작해서, setup.ts로 환경을 준비하고, commands.ts와 tools.ts에서 도구를 로딩하고, QueryEngine.ts가 사용자 입력을 받아서 처리하고, history.ts와 cost-tracker.ts가 기록을 남긴다.
부트스트랩
Claude Code가 실행되면 바로 프롬프트가 뜨는 게 아니다. 내부적으로 7단계의 부트스트랩 과정을 통해 시스템이 켜진다.
1
2
3
4
5
6
7
1. top-level prefetch side effects
2. warning handler and environment guards
3. CLI parser and pre-action trust gate
4. setup() + commands/agents parallel load
5. deferred init after trust
6. mode routing: local / remote / ssh / teleport / direct-connect / deep-link
7. query engine submit loop
이번에도 간단한 식당 비유와 함께 순서대로 하나씩 보자.
1단계 (prefetch) 는 식당 문을 열기 전에 식재료 발주를 미리 넣어두는 것이다. MDM 데이터를 미리 읽고, 키체인을 프리페치하고, 프로젝트를 스캔한다. 나중에 필요할 데이터를 미리 요청해두는 것이다.
2단계 (environment guards) 는 가스 밸브 확인이다. 경고 핸들러를 설정하고, 실행 환경이 안전한지 확인한다.
3단계 (trust gate) 는 “이 환경에서 실행해도 안전한가?” 를 판단하는 단계로 중요한 단계다. CLI 인자를 파싱하고, 사용자가 신뢰할 수 있는 환경에서 실행하고 있는지 확인한다. 이 관문을 통과해야만 다음 단계로 넘어간다.
4단계 (parallel load) 는 주방 도구를 꺼내는 과정이다. 207개 커맨드와 184개 툴을 병렬로 로딩한다. 직렬로 순차 로딩하면 시작이 느려지기 때문이다.
5단계 (deferred init) 는 trust gate를 통과한 후 나중에 실행되는 초기화다. 플러그인, 스킬, MCP 프리페치, 세션 훅 같은 것들이 여기서 활성화된다. 신뢰할 수 없는 환경이라면 이 단계는 전부 건너뛴다.
왜 deferred init이 trust gate 뒤에 있을까? 신뢰할 수 없는 환경에서 플러그인을 로딩하면 악성 코드가 실행될 수 있기 때문이다. “먼저 신뢰를 확인하고, 신뢰된 뒤에야 위험한 기능을 활성화한다.” 이 패턴은 보안이 중요한 어떤 시스템에서든 적용할 수 있는 설계 원칙이다.
6단계 (mode routing) 는 어떤 모드로 실행할지 결정한다. 로컬 실행인지, 원격 제어인지, SSH 프록시인지, 텔레포트(세션 이동)인지, 다이렉트 커넥트인지, 딥링크인지. 모드가 6가지나 된다는 건, Claude Code가 단순한 로컬 CLI를 훨씬 넘어선 시스템이라는 뜻이다.
7단계 (query engine loop) 에서 드디어 프롬프트가 뜨고, 사용자 입력을 받기 시작한다. 여기서부터 턴 루프가 시작된다.
아키텍처
지금까지 살펴본 내용을 하나의 전체 흐름으로 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[사용자 입력]
│
▼
[부트스트랩 (7단계)]
│ prefetch → 환경 검사 → trust gate
│ → 커맨드/툴 병렬 로딩 → deferred init → 모드 라우팅
│
▼
[쿼리 엔진 (QueryEngine)]
│ 사용자 프롬프트를 받아서 분석
│
├──→ [커맨드 라우터] ──→ 207개 커맨드 중 매칭
│ /review → 코드 리뷰
│ /plan → 작업 계획
│ /commit → 커밋
│
├──→ [툴 라우터] ──→ 184개 툴 중 매칭
│ BashTool → 셸 명령 실행
│ FileEditTool → 파일 수정
│ AgentTool → 서브 에이전트 포크
│
├──→ [퍼미션 체크]
│ deny list 확인
│ 위험 명령 차단
│ 사용자 확인 요청
│
└──→ [세션 관리]
토큰 예산 확인
트랜스크립트 저장
컨텍스트 압축
│
▼
[결과 반환 → 다음 턴]
사용자가 “이 버그 고쳐줘”라고 입력하면, 쿼리 엔진이 이걸 받아서 FileReadTool로 관련 파일을 읽고, LLM이 수정 방안을 판단하면 FileEditTool로 코드를 수정하고, BashTool로 테스트를 실행한다. 이 과정에서 매 단계마다 퍼미션이 체크되고, 토큰 사용량이 기록되고, 세션 히스토리에 추가된다.
하나의 “버그 고쳐줘”라는 요청이 내부적으로는 여러 턴의 도구 호출로 분해되어 실행되는 것이다. 이 턴 루프를 관리하는 게 QueryEngine의 핵심 역할이다.
마치며
Claude Code는 부트스트랩, 커맨드/툴 라우팅, 퍼미션 시스템, 세션 관리, 멀티 에이전트, 원격 연결까지 갖춘 대규모 프로덕션 소프트웨어다.
이 복잡한 시스템이 결국 하나의 질문에 답하기 위해 존재한다. “사용자가 뭔가를 요청했을 때, 어떻게 안전하고 정확하게 처리할 것인가.”
이런걸 설계하는 사람들이 대단하다고 생각한다.
