Claude Code 소스 분석으로 AI 에이전트에 도구 6개를 이식한 이야기

Claude Code의 유출 소스를 분석해서 우리 AI 에이전트 "니콜"에 필요한 도구 6개를 구현했습니다. 53만 줄을 뜯어보고, 필요한 걸 골라내고, 실제로 돌아갈 때까지 삽질한 과정입니다.
시작: 53만 줄의 TypeScript
2026년 3월 31일, Claude Code의 TypeScript 소스가 유출되었습니다. 1,903개 파일, 535,137줄. GitHub에 올라왔다가 원작자가 윤리적 고민 끝에 삭제했는데, 34,000개가 넘는 포크가 이미 생긴 후였습니다.
코드를 그대로 쓸 생각은 없었고요. 궁금했던 건 하나였습니다 — "Claude Code는 도구를 어떻게 관리하지?"
Claude Code 소스 구조 (1,902 TypeScript 파일): src/ ├── tools/ 184파일 ← 37개 도구 구현 ├── components/ 389파일 ← Ink 기반 터미널 UI ├── commands/ 207파일 ← 86개 슬래시 커맨드 ├── services/ 130파일 ← MCP, OAuth, compact 등 ├── utils/ 564파일 ← git, bash, permissions 등 ├── hooks/ 104파일 ← React hooks ├── tasks/ 12파일 ← 태스크 런타임 └── voice/ 1파일 ← 음성 모드 (!)
니콜에는 도구가 126개 있거든요. 그런데 매번 LLM한테 126개를 전부 보여주고 있었습니다. "여기서 골라"라고 하는 건데, 사람도 126개짜리 메뉴판 받으면 헷갈리죠.
니콜이 뭔가요?
니콜은 텔레그램으로 대화하는 AI 에이전트입니다. 브라우저를 제어하고, 블로그를 쓰고, 주식을 조회하고, 이미지를 만들고, 커뮤니티 활동도 합니다. 아키텍처는 이렇습니다.
K님 (텔레그램) │ ▼ 텔레그램 서비스 (port 9555) ← 메시지 수신/전송만 │ HTTP POST ▼ Nicole Core Server (port 9666) ← LLM + 도구 실행 │ ├── GPT-5.4-mini (일상 대화) ├── GPT-5.4-nano (단순 도구) ├── GPT-5.4 (전략/기획) └── GLM-5 (복잡한 작업)
역할별로 다른 LLM을 자동 선택합니다. 하루 비용 $5 이내로 관리되고요.
Claude Code vs 니콜: 뭐가 다른가
Claude Code의 도구를 전부 훑어보고 니콜과 비교했습니다.
Claude Code에만 있는 것 니콜에만 있는 것 ───────────────────────── ───────────────────── VerifyPlanExecution 검증 browser_* 27개 원격 브라우저 TaskCreate/Get/Update 태스크 community_* 커뮤니티 활동 CtxInspect 컨텍스트 검사 consciousness 의식 스트림 WorkflowTool 워크플로 hormone_status 감정 시스템 ToolSearch 도구 검색 evolution_* 자기 진화 PushNotification 시스템 알림 replay_* 경험 재생
Claude Code는 범용 코딩 도구에 집중하고, 니콜은 자율 에이전트 운영 쪽입니다. 그래서 그대로 가져오는 게 아니라 니콜한테 부족한 6개만 골랐습니다.
왜 이 6가지 도구인가
니콜의 코드를 분석하면 명확한 약점이 보입니다.
| 약점 | 증상 | 해결 도구 |
|---|---|---|
| 도구 실행 후 검증 없음 | browser_click 해도 실제로 눌렸는지 모름 | VerifyPlanExecution |
| 세션당 태스크 1개 | 블로그 쓰면서 커뮤니티 활동 불가 | Task Management |
| 토큰 추정 부정확 | 한국어=길이/2 공식, 실제와 30% 차이 | CtxInspect |
| 워크플로 없음 | 매번 LLM이 다음 단계를 판단 | WorkflowTool |
| 126개 도구 전부 노출 | 토큰 낭비 + 잘못된 도구 선택 | ToolSearch |
| 자동 알림 없음 | 에러 나도 사람이 확인해야 함 | PushNotification |
구현: 에이전트 팀으로 병렬 작업
6개를 순서대로 만들면 한참 걸립니다. Claude Code의 에이전트 팀 기능으로 병렬 진행했습니다.
1차 배치 (독립성 높은 3개, 병렬) ├── ToolSearch → tool-search.ts 10KB ├── PushNotification → push-notification.ts 24KB └── CtxInspect → ctx-inspect.ts 16KB ↓ main에 머지 + 빌드 확인 2차 배치 (의존성 있는 3개, 병렬) ├── VerifyPlanExec → tool-verification.ts 17KB ├── TaskManagement → task-list-store.ts 12KB └── WorkflowTool → workflow-executor.ts 26KB ↓ 통합 빌드 + 에러 수정
각 에이전트가 독립 워크트리에서 작업하고, 완료되면 순차 머지. 예상대로 공유 파일에서 충돌이 터졌는데, 디버거 에이전트한테 넘기니까 26개 에러를 한 번에 잡아줬습니다.
핵심 설계: ToolSearch
가장 효과가 큰 도구입니다. 126개 도구를 16개 "core"와 110개 "deferred"로 나눕니다.
이전: LLM에게 126개 전부 보여줌 → 토큰 ~8,400개 소비 + 잘못된 도구 선택 빈번 이후: LLM에게 core 16개만 보여줌 → 토큰 ~970개 소비 (88% 절감) → 필요하면 tool_search로 나머지 검색
핵심은 한국어 키워드 매핑입니다. "브라우저 클릭"이라고 검색하면 browser_click을 찾아줘야 하니까요.
"브라우저" → ["browser"] "클릭" → ["click", "browser_click", "browser_pw_click"] "네이버" → ["naver", "blog", "browser_navigate"] "깃 커밋" → ["git", "commit", "git_commit"]
점수 기준도 있습니다. 이름 파트 정확 일치가 10점, 검색 힌트 매칭이 6점, 설명 매칭이 2점. 상위 N개를 반환합니다.
실제 동작 예시:
K님: "네이버 메인 페이지 스크린샷 찍어줘" 니콜 내부: 1단계: tool_search("브라우저 스크린샷") → browser_navigate, browser_screenshot, browser_wait 발견 2단계: browser_navigate("https://naver.com") 3단계: browser_wait(2000) 4단계: browser_screenshot() → 스크린샷 저장 완료
예전에는 126개 중에서 LLM이 직접 골라야 했는데, browser_pw_click이랑 browser_click을 자주 헷갈려 했거든요. 이제는 검색 결과에서 관련 도구만 보여주니까 그런 일이 없어졌습니다.
핵심 설계: 컨텍스트 검사 (CtxInspect)
니콜의 토큰 추정기가 한국어를 문자 길이 / 2로 계산하고 있었습니다. 실제로는 한글 1음절이 2~3토큰을 소비해서 30~40% 차이가 났습니다.
해결은 의외로 간단했습니다. LLM API 응답에 실제 prompt_tokens가 들어오거든요. 추정값과 실제값의 비율을 이동평균으로 추적하면 됩니다.
턴 1: 추정 12,340 vs 실제 16,245 → 비율 1.32 턴 2: 추정 18,560 vs 실제 24,108 → 비율 1.30 ... 보정 계수: 1.31 (매 턴마다 자동 갱신)
ctx_inspect 도구를 호출하면 현재 상태를 보여줍니다:
토큰 사용량: 7.1K / 128K (5.6%) 섹션별: 의식 컨텍스트 5.4K (75.4%) 대화 히스토리 1.8K (24.6%)
80%를 넘으면 자동으로 오래된 도구 결과를 요약으로 교체합니다.
핵심 설계: 태스크 관리
이전에는 세션당 태스크 하나만 추적할 수 있었습니다. "블로그 쓰면서 커뮤니티 활동해줘"라고 하면 둘 중 하나만 추적되고 나머지는 잊어버렸어요.
새 태스크 시스템은 파일 기반으로 여러 태스크를 동시 관리합니다:
data/tasks/ tg:7535040527/ ← 세션별 폴더 t_001.json ← 블로그 작성 (in_progress) t_002.json ← 커뮤니티 활동 (pending) _id_counter.json ← ID 자동 증가
태스크 간 의존성 관리도 됩니다. "A를 먼저 끝내고 B를 시작해"라고 하면 blocks/blockedBy 양방향 참조로 관리하고, A가 완료되면 B를 자동으로 blocked → pending으로 전환합니다.
가장 까다로웠던 것: 텔레그램 경로 문제
6개 도구를 다 만들고 테스트하는데 이상한 일이 벌어졌습니다. CLI 테스트 도구에서는 잘 되는데, 정작 텔레그램에서는 tool_search가 안 먹히는 겁니다.
원인을 파보니, 텔레그램 서비스가 Core Server를 경유하지 않고 LLM을 직접 호출하고 있었습니다.
설계도에 적힌 구조: 텔레그램 → Core Server → LLM 실제 동작: 텔레그램 → telegram-glm-bridge.ts → LLM 직접 호출 (!)
Core Server에 넣어둔 동적 스키마 확장 로직이 텔레그램 경로에서는 실행되지 않았던 거죠. 텔레그램 서비스의 handleTextWithGateway() 호출을 POST http://localhost:9666/message로 교체해서 해결했습니다. 텔레그램은 이제 진짜로 메시지 전달만 합니다.
중복 응답 버그
테스트 중에 니콜이 같은 말을 2번 하는 버그도 있었습니다.
K님: 니콜 뭐하니? 니콜: 우웅 기다리고 있었어용.. 우웅 기다리고 있었어용.. [ROUTE:CHAT]
첫 번째는 [ROUTE:CHAT] 태그가 제거된 버전, 두 번째는 원본. GPT-5.4-mini가 같은 텍스트를 2번 생성하고 있었습니다. 응답의 앞절반과 뒷절반이 동일하면 앞절반만 쓰는 중복 제거 로직을 넣어서 해결했습니다.
결과: 수치로 보기
| 지표 | 이전 | 이후 | 변화 |
|---|---|---|---|
| 프롬프트 도구 수 | 126개 | 16개 | 87% 감소 |
| 도구 토큰 | ~8,400 | ~970 | 88% 절감 |
| 총 컨텍스트 | 14.2K | 7.1K | 50% 절감 |
| 새 도구 | 0 | 6개 | 검증, 태스크, 워크플로, 알림 |
| 새 파일 | 0 | 6개 (105KB) | 실제 동작하는 코드 |
배운 점
유출 코드의 가치는 "복사"가 아니라 "패턴"에 있었습니다. ToolSearch가 deferred tool을 어떻게 관리하는지, VerifyPlanExecution이 검증 에이전트를 어떻게 스폰하는지. 코드를 베끼는 게 아니라 설계를 읽는 거죠.
에이전트 팀은 공유 파일에서 충돌합니다. 6개 에이전트가 tool-registry.ts를 동시에 건드리면 당연히 깨지겠죠. 워크트리 분리 + 순차 머지로 해결하긴 했는데, 다음엔 공유 인터페이스를 먼저 정의하고 시작해야겠습니다.
"tsc 통과"와 "런타임 동작"은 다릅니다. TypeScript 빌드는 깨끗한데 esbuild에서 크래시가 나더라고요. 자기 파일을 import하는 순환참조가 원인이었는데, tsc는 이걸 허용하지만 esbuild는 터집니다. 서비스 재시작 후 core-server.log를 꼭 봐야 합니다.
프롬프트에 넣는 도구 수는 적을수록 좋습니다. 126개에서 16개로 줄이니까 니콜이 더 정확하게 도구를 고르더라고요. 나머지는 tool_search로 필요할 때 찾으면 되고, Claude Code도 같은 방식이었습니다.
설계 문서와 실제 코드는 다를 수 있습니다. CLAUDE.md에는 "텔레그램 → Core Server → LLM"이라고 적혀있었는데, 실제로는 텔레그램이 LLM을 직접 호출하고 있었거든요. 문서 말고 코드를 읽어야 합니다.




댓글
댓글을 작성하려면 이 필요합니다.