이 글은 Claude Code의 내부 소스 코드가 npm source map 유출로 인해 공개된 사건을 다루며, 이를 통해 드러난 Claude Code의 복잡하고 정교한 아키텍처를 상세히 분석합니다. Anthropic이 '오픈소스'라고 불렀던 것과는 달리, 핵심 엔진은 상용 비공개 코드였으며, 이 내부 코드에는 8겹의 보안 레이어, 4단계 메시지 압축, 비용 인식을 통한 에러 복구 등 실제 프로덕션 환경에 최적화된 설계가 담겨 있습니다. 또한, 미출시 기능들을 통해 Anthropic의 미래 로드맵(음성 모드, 멀티 에이전트, 선제적 Kairos 모드 등)이 드러나, 에이전틱 AI 시스템 개발에 있어 중요한 통찰을 제공하고 있습니다.


1. 무슨 일이 있었나요? 😮

2026년 3월 31일, 보안 연구자 Chaofan Shou 님이 X(이전 트위터)에 흥미로운 스크린샷 몇 장을 올렸어요. Anthropic의 Claude Code 내부 소스 코드가 npm 패키지에서 통째로 유출되었다는 내용이었죠. 바로 .map 파일, 즉 source map 파일이 프로덕션 빌드에서 제거되지 않고 그대로 포함되어 있었던 거예요! 보통 개발 과정에서만 사용하고 배포 시에는 삭제해야 하는 파일인데 말이죠. 😅

이 소식이 전해지자마자, Reddit의 r/LocalLLaMA 커뮤니티는 몇 시간 만에 유출된 코드베이스를 분석하기 시작했어요. 이건 의도적인 해킹이나 정교한 사회공학 공격이 아니었어요. 단순히 빌드 파이프라인에서 .map 파일을 제외하는 작은 실수가 엄청난 결과를 초래한 거죠. 이 사건으로 Anthropic이 Claude Code를 얼마나 정교하게 설계했는지와 동시에 얼마나 허술하게 배포했는지가 모두 드러나 버렸어요. 경쟁사와 보안 연구자들에게는 뜻밖의 선물이 된 셈이죠! 🎁

여기서 한 가지 짚고 넘어가야 할 점이 있어요. Anthropic은 그동안 Claude Code를 "오픈소스"라고 홍보해왔어요. 실제로 공식 GitHub 저장소도 있어서 누구나 코드를 볼 수 있었죠. 하지만 그곳에 있었던 건 뭘까요? 🤔 대부분 플러그인 시스템, Hook 예제, 설정 파일 템플릿, 그리고 몇몇 예제 플러그인뿐이었어요. 요리로 비유하자면, 레스토랑 메뉴판이랑 테이블 세팅만 공개하고 "우리 주방은 오픈입니다!"라고 말한 격이었죠. 🧑‍🍳

이번에 npm source map을 통해 드러난 것은 바로 그 주방 전체였어요. TypeScript/React로 작성된 4,600개 이상의 소스 파일, 55개 이상의 디렉토리로 이루어진 방대한 코드였죠. 게다가 이 코드의 라이선스는 Apache 2.0이 아니라 Anthropic Commercial Terms of Service였어요. 그러니 진정한 오픈소스라고는 할 수 없겠죠.

이 글에서는 법적, 윤리적 논쟁은 잠시 미뤄두고, 유출된 코드가 보여주는 기술적인 아키텍처를 깊이 파고들어 보려 해요. 솔직히 말하면, 이 코드를 보면 꽤 인상 깊답니다. 실수로 유출된 부분만 빼면요! 😉


2. 전체 아키텍처: 생각보다 훨씬 거대했어요! 🤯

처음 Claude Code를 접했을 때, "어? 이거 그냥 API를 감싼 래퍼(wrapper) 아닌가?" 하고 생각하기 쉬웠어요. 터미널에서 Claude에게 말을 걸고, Claude가 코드를 고쳐주면 끝나는, 아주 단순한 서비스처럼 보였으니까요.

하지만 유출된 코드를 열어보는 순간, 그런 생각은 산산조각 났답니다. 💥

기술 스택

Claude Code가 어떤 기술로 만들어졌는지 살펴볼까요?

레이어기술왜 이 선택인가
런타임BunNode.js보다 빠른 시작 시간과 네이티브 TypeScript 지원. feature() 번들링 API로 빌드 타임에 사용하지 않는 코드를 제거할 수 있어요.
언어TypeScript (strict mode)4,600개 이상의 파일 규모 코드베이스에서 타입 안정성(오류를 줄여주는 기능)은 선택이 아니라 필수였겠죠.
UIReact 18 + [Ink](https://github.com/vadimdemedes/ink)터미널에서도 React 컴포넌트를 사용하여 UI를 렌더링해요. 권한 다이얼로그, 진행 표시줄, 멀티패널 레이아웃 등 복잡한 UI를 구현하기 위해 이 선택이 정당화됩니다.
API 클라이언트@anthropic-ai/sdkAnthropic의 공식 SDK를 사용했어요.
MCP 클라이언트@modelcontextprotocol/sdk외부 도구 서버 연동을 위한 표준 프로토콜입니다.
피처 플래그GrowthBook서버에서 특정 기능을 켜고 끄거나, A/B 테스트를 진행하기 위한 도구예요.
번들러Bun bundlerfeature() 함수를 기반으로 사용하지 않는 코드를 제거하여, 내부/외부 빌드를 효율적으로 분리할 수 있게 해줍니다.

터미널 앱에 React를 썼다는 게 좀 과하다고 느껴질 수도 있어요. 하지만 코드를 보면 PermissionDialog.tsx(권한 다이얼로그), WorkerBadge, 멀티패널 레이아웃, 실시간 스트리밍 UI 같은 복잡한 상호작용들이 많아서 이 선택이 이해가 갑니다. 단순한 텍스트 출력만 하는 터미널이 아니라, 상당히 풍부한 사용자 인터페이스를 제공하고 있었던 거죠.

공식 오픈소스 vs 유출 코드: 숫자로 보는 괴리

Anthropic이 "오픈소스"라고 주장했던 것과 실제 유출된 코드 사이에는 얼마나 큰 차이가 있었을까요? 숫자로 비교해보면 확연히 드러나요.

공식 저장소 (anthropics/claude-code)유출 코드
파일 수~279개 (스크립트/설정 위주)4,600개 이상 (풀스택 엔진)
핵심 엔진미포함포함
도구 구현체미포함전체 포함 (Bash, Read, Write, Edit 등)
에이전틱 루프미포함포함 (query.ts 1,729줄)
권한 시스템미포함포함 (permissions.ts 52K)
API 통신미포함포함 (스트리밍, 캐싱, 폴백)
Bridge/원격미포함포함 (33개 이상 파일)
MCP 클라이언트미포함포함 (client.ts 119K)
라이선스Anthropic Commercial ToS비공개 상용 코드

279개 파일과 4,600개 파일이라니, 엄청난 차이죠? 😮 이쯤 되면 "오픈소스"라는 단어의 의미에 대해 다시 한번 생각해 볼 필요가 있을 것 같아요.


3. 에이전틱 루프: Claude Code의 심장을 들여다볼까요? ❤️‍🔥

전체 시스템 아키텍처

Claude Code의 가장 중요한 부분, 바로 심장이라고 할 수 있는 것은 query.ts 파일이에요. 무려 1,729줄이나 되는 이 파일 안에는 while(true) 루프가 존재하는데, 이걸 "에이전틱 루프"라고 불러요. 🔄 이 루프는 사용자 입력을 받아서 적절한 도구를 실행하고, 그 결과를 다시 Claude에게 전달하는 전체 사이클을 관장하는 역할을 한답니다.

3.1 Async Generator: 아주 우아한 설계 선택 ✨

가장 먼저 눈에 띄는 건 함수의 형태예요.

export async function* query(
  params: QueryParams,
): AsyncGenerator<
  | StreamEvent
  | RequestStartEvent
  | Message
  | TombstoneMessage
  | ToolUseSummaryMessage,
  Terminal  // 반환값: 종료 이유
>

async function*async generator라는 건데요. 이벤트를 yield라는 키워드로 계속 스트리밍(실시간으로 데이터를 보내는 것)하면서, 최종적으로 루프가 끝날 때는 Terminal이라는 타입의 값을 return해요. 이게 왜 영리한 선택일까요? 🤔

일반적으로는 이벤트를 보낼 때 EventEmitter나 콜백(callback)을 사용했을 거예요. 하지만 async generator를 사용하면 이벤트 스트림과 루프의 종료 시점 처리를 하나의 함수 안에서 깔끔하게 관리할 수 있어요. 사용자는 for await...of 구문을 써서 이벤트를 받다가, 루프가 끝나면 return 값에서 종료 이유(Terminal)를 확인하는 거죠. 만약 에러가 발생하면 generator 안에서 throw해서 소비자의 try-catch 구문으로 에러를 전달할 수 있어서, 에러 처리도 자연스럽답니다.

복잡한 상태 변화를 표현하기에 정말 깔끔한 패턴이며, 특히 에이전트처럼 반복적인 작업을 수행하는 루프에 아주 잘 어울리는 설계라고 할 수 있어요.

3.2 불변 파라미터 + 가변 상태: Continue Site 패턴 🔄

이 루프는 두 종류의 데이터를 명확하게 분리해서 사용해요.

불변 파라미터 — 루프가 도는 내내 변하지 않는 값들이에요.

type QueryParams = {
  messages: Message[];
  systemPrompt: SystemPrompt;
  canUseTool: CanUseToolFn; // 권한 검사 콜백 함수
  toolUseContext: ToolUseContext; // 도구 실행 시 필요한 정보
  taskBudget?: { total: number }; // API에 할당된 예산 (베타 기능)
  maxTurns?: number; // 최대 실행 턴 수 제한
  fallbackModel?: string; // 주 모델 실패 시 대체할 모델
  querySource: QuerySource; // 쿼리가 어디서 왔는지 (REPL, agent 등)
  // ... 기타 파라미터
};

가변 상태 — 루프가 한 번 돌 때마다 갱신되는 값들이에요.

type State = {
  messages: Message[];
  toolUseContext: ToolUseContext;
  autoCompactTracking: AutoCompactTrackingState | undefined;
  maxOutputTokensRecoveryCount: number;
  hasAttemptedReactiveCompact: boolean;
  maxOutputTokensOverride: number | undefined;
  pendingToolUseSummary: Promise<ToolUseSummaryMessage | null> | undefined;
  stopHookActive: boolean | undefined;
  turnCount: number;
  transition: Continue | undefined; // 이전 턴이 계속된 이유
};

여기서 주목해야 할 패턴은 바로 "Continue Site"예요. 소스 코드 주석에서도 직접 이렇게 설명하고 있답니다.

// Continue sites write `state = { ... }` instead of 9 separate assignments.

상태를 변경할 때, 9개의 개별 필드를 하나씩 수정하는 대신, 전체 상태 객체를 새로 할당하는 방식이죠.

state = {
  ...state,
  messages: newMessages,
  turnCount: nextTurnCount,
  transition: { reason: "next_turn" },
};

이 패턴의 장점은 두 가지예요. 첫째, 상태 변경이 원자적(atomic)이라는 점이에요. 즉, 9개 필드 중에 일부만 업데이트되고 에러가 나서 중간 상태가 되는 일이 없다는 거죠. 둘째, transition 필드를 통해 왜 다음 턴으로 계속 진행했는지를 추적할 수 있어서, 테스트 시에도 메시지 내용을 일일이 확인하지 않고 복구 경로가 제대로 작동했는지 쉽게 확인할 수 있어요.

마치 React의 setState 철학이 백엔드 루프에까지 스며든 것 같은데요, Anthropic 엔지니어들이 React를 얼마나 사랑하는지 알 수 있는 부분이죠. 💖

이 패턴이 중요한 이유는 에이전트 루프에서 상태 관리 버그가 발생하면 곧바로 사용자 비용으로 이어질 수 있기 때문이에요. 상태가 꼬이면 불필요한 API 호출이 발생하고, 이는 곧 토큰 비용으로 청구되니까요. Cursor나 Windsurf 같은 경쟁 제품들도 비슷한 에이전트 루프를 구현하고 있겠지만, 이 정도 수준의 엄밀한 상태 관리를 갖추고 있는지는 코드가 공개되지 않아 알 수 없죠.

3.3 턴당 6단계 파이프라인: 자세히 살펴봐요 🧐

각 턴은 다음 6단계를 거쳐 진행돼요. 소스 코드의 실제 라인 번호와 함께 자세히 살펴볼게요.

1단계: Pre-Request Compaction (API 요청 전 압축) (lines 365-548) 🧹

API를 호출하기 전에 대화 기록(히스토리)을 정리하는 단계예요. 5가지 압축 메커니즘이 순서대로 적용된답니다.

// 1. Tool Result Budget 적용 (lines 369-394)
messagesForQuery = await applyToolResultBudget(
  messagesForQuery,
  toolUseContext.contentReplacementState,
  // 에이전트/REPL 소스만 교체 기록을 저장 (재개(resume)를 위해)
  persistReplacements ? records => void recordContentReplacement(...) : undefined,
)
// 2. Snip Compact — 가장 저렴하고 과격 (lines 401-410)
if (feature('HISTORY_SNIP')) {
  const snipResult = snipModule!.snipCompactIfNeeded(messagesForQuery)
  messagesForQuery = snipResult.messages
  snipTokensFreed = snipResult.tokensFreed
}
// 3. Microcompact — 캐시 인식 도구 결과 클리어 (lines 413-426)
const microcompactResult = await deps.microcompact(messagesForQuery, toolUseContext, querySource)
messagesForQuery = microcompactResult.messages
// 4. Context Collapse — 단계적 축소 (lines 440-447)
if (feature('CONTEXT_COLLAPSE') && contextCollapse) {
  const collapseResult = await contextCollapse.applyCollapsesIfNeeded(...)
  messagesForQuery = collapseResult.messages
}
// 5. Auto-Compact — 임계값 초과 시 전체 요약 (lines 453-543)
const { compactionResult, consecutiveFailures } = await deps.autocompact(...)

이 압축 순서가 아주 중요해요. Context Collapse가 Auto-Compact 앞에 오는 이유는 소스 코드 주석에 명확히 설명되어 있어요.

// Runs BEFORE autocompact so that if collapse gets us under the
// autocompact threshold, autocompact is a no-op and we keep granular
// context instead of a single summary.

즉, Collapse로 충분히 대화 길이가 줄어들면, Auto-Compact(비용이 많이 드는 API 호출)가 작동하지 않게 하는 거죠. 이는 세밀한 컨텍스트(대화 내용)를 최대한 보존하면서도 비용을 아끼려는 영리한 전략이랍니다.

압축이 끝나면 서버가 전체 히스토리를 볼 수 없으므로, 클라이언트가 작업 예산(task budget)의 남은 양을 직접 추적해서 서버에 알려줘야 해요. 간단해 보이지만, 압축 경계에서 서버와 클라이언트 간의 상태를 동기화하는 것은 에이전트 시스템에서 자주 발생하는 미묘한 문제 중 하나랍니다.

2단계: API Call & Streaming (API 호출 및 스트리밍) (lines 659-863) 🚀

for await (const message of deps.callModel(
  fullSystemPrompt,
  prependUserContext(messagesForQuery, userContext),
  toolUseContext,
  {
    taskBudget,
    taskBudgetRemaining,
    maxOutputTokensOverride,
    skipCacheWrite,
  },
)) {
  // 스트리밍 이벤트 처리
}

여기서 핵심은 StreamingToolExecutor예요. Claude가 응답을 생성하는 동안 도구가 병렬로 실행되는 거죠! 🏃‍♀️🏃‍♂️ Claude가 "파일을 읽어볼게요"라고 타이핑하는 순간, 이미 파일이 읽히기 시작한다는 뜻이에요. 사용자가 느끼는 대기 시간을 줄여주는 비결 중 하나죠.

소스를 보면 이 병렬 실행의 구현이 상당히 정교하다는 것을 알 수 있어요.

// StreamingToolExecutor.ts
private canExecuteTool(isConcurrencySafe: boolean): boolean {
  const executingTools = this.tools.filter(t => t.status === 'executing')
  return (
    executingTools.length === 0 ||
    (isConcurrencySafe && executingTools.every(t => t.isConcurrencySafe))
  )
}

모든 도구에는 isConcurrencySafe라는 플래그(특정 상태를 나타내는 표시)가 있어요. FileReadTool, GlobTool, GrepTool처럼 읽기만 하는 도구는 병렬 실행이 안전해요. 하지만 FileWriteTool이나 BashTool처럼 시스템 상태를 변경하는 도구는 순차적으로 실행해야 하죠. 만약 한 도구가 에러를 내면, 그와 관련된 다른 도구들도 sibling_error로 취소된답니다.

type AbortReason =
  | "sibling_error" // 같은 종류의 도구 에러 → 나도 취소
  | "user_interrupted" // 사용자가 Ctrl+C 또는 ESC 눌러 중단
  | "streaming_fallback"; // 모델 폴백으로 인해 폐기됨

모델 폴백(fallback, 주 모델이 실패할 경우 대체 모델을 사용하는 것)도 이 단계에서 처리돼요. 주 모델이 실패하면:

if (innerError instanceof FallbackTriggeredError && fallbackModel) {
  currentModel = fallbackModel;
  attemptWithFallback = true;
  // 고아(orphan) 메시지에 tombstone 생성
  yield *
    yieldMissingToolResultBlocks(
      assistantMessages,
      "Model fallback triggered",
    );
  // StreamingToolExecutor 초기화 후 재시도
}

Tombstone 메시지는 "이 도구 호출은 모델 폴백으로 인해 폐기되었습니다"라는 기록을 남기는 거예요. 대화 기록의 일관성을 유지하기 위한 장치이죠.

3단계: 에러 복구 캐스케이드 (lines 1062-1256) 🩹

이 부분이 Claude Code 아키텍처에서 가장 인상 깊은 부분 중 하나예요. 에러가 발생했다고 해서 바로 포기하지 않아요. 🙅‍♀️ 대신 저비용에서 고비용 순서로 복구를 시도하는 전략을 사용하죠.

프롬프트 길이 초과 (413 에러) 복구 — 3단계 캐스케이드:

  1. 1단계: Context Collapse 드레인 (비용: 0)
    • 이미 준비된 축소(collapse)를 바로 적용해요. 추가 API 호출 없이 즉시 실행되죠.
  2. 2단계: Reactive Compact (비용: API 1회 호출)
    • 전체 대화를 요약해요. 이미지 같은 미디어 파일을 제거한 뒤, 요약된 내용으로 다시 시도하죠. 만약 요약 자체도 너무 크면, 미디어를 제거하고 다시 한번 시도하는 "strip retry"도 있어요.
  3. 3단계: 에러 표출
    • 모든 시도가 실패하면 사용자에게 에러를 보여줍니다.

최대 출력 토큰 초과 복구 — 역시 3단계:

  1. 1단계: 토큰 캡 에스컬레이션 (비용: 0)
    • 초과한 토큰 상한을 8K에서 64K(ESCALATED_MAX_TOKENS)로 투명하게 올려줘요. 사용자에게는 아무런 알림 없이 자동으로 처리됩니다.
  2. 2단계: Resume 메시지 주입 (비용: API 재호출, 최대 3회)
    • "이전 응답이 잘렸습니다. 잘린 부분부터 이어서 작성해주세요"라는 메시지를 모델에 주입하고 다시 호출해요. maxOutputTokensRecoveryCount를 추적해서 최대 3회까지만 시도합니다.
  3. 3단계: 복구 소진
    • 3회 시도 후에도 문제가 해결되지 않으면, 현재까지의 결과로 완료 처리합니다.

이 캐스케이드의 설계 원칙은 아주 명확해요. 첫 번째 시도는 항상 무료(free)여야 한다는 거죠. 전체 대화를 요약하는 것처럼 비용이 많이 드는 작업은 최후의 수단으로 사용해요. 이건 단순히 "재시도"가 아니라, 비용과 효과를 정밀하게 고려한 복구 전략이랍니다.

4단계: Stop Hooks & Token Budget (종료 훅 및 토큰 예산) (lines 1267-1355) 🛑💰

Stop hook은 사용자 정의 검증 로직을 실행하는 기능이에요. 예를 들어, "테스트를 통과하지 않으면 작업을 멈추지 마"라는 훅을 걸어두면, Claude가 작업을 끝내려 할 때 이 훅이 실행되고, 만약 실패하면 훅의 에러 메시지가 대화에 주입되어 Claude가 다시 시도하도록 만들 수 있어요.

토큰 버짓(예산)은 더 흥미로워요.

function checkTokenBudget(tracker, budget, globalTurnTokens) {
  const pct = (turnTokens / budget) * 100
  const isDiminishing = (
    continuationCount >= 3 &&
    deltaSinceLastCheck < 500 &&  // DIMINISHING_THRESHOLD
    lastDeltaTokens < 500
  )
  if (!isDiminishing && turnTokens < budget * 0.9) {
    return { action: 'continue', nudgeMessage: ... }
  }
  return { action: 'stop', completionEvent: { diminishingReturns, ... } }
}

감소 수익(diminishing returns) 감지 기능이 내장되어 있어요. 만약 3회 연속으로 작업을 계속했는데 매번 500 토큰도 생성하지 못하면, 시스템은 "이건 더 해봤자 의미 없다"고 판단하고 작업을 멈춰버려요. 📉 토큰 예산의 90%를 소진하기 전까지는 계속 시도하되, 불필요한 노력은 하지 않는 거죠.

이 로직이 왜 중요할까요? 에이전트 루프에서 가장 위험한 상황은 무한 루프에 빠지는 거예요. 😵‍💫 Claude가 "한 번만 더 고쳐볼게요"를 반복하면서 토큰만 계속 소비하고 실제로는 진전이 없는 상황을 방지하기 위함이죠. 이 감소 수익 감지 기능이 그런 상황을 자동으로 차단해준답니다.

5단계: Tool 실행 (도구 실행) (lines 1363-1520) 🛠️

이 단계에서는 스트리밍 모드(2단계에서 이미 시작)와 배치 모드(한 번에 모아서 처리하는 모드)가 공존해요.

// 스트리밍: 병렬 안전 도구는 API 응답 생성 중에 이미 실행
if (streamingToolExecutor) {
  toolResults = await streamingToolExecutor.getRemainingResults();
}
// 배치: 나머지 도구는 여기서 순차 실행
toolResults = await runTools(toolUseBlocks, toolUseContext, canUseTool);

진행 상황 알림 기능도 있어요. progressAvailableResolve라는 Promise 객체를 통해 "새로운 진행 상황이 발생했다"는 것을 소비자에게 알려주는데, 이것이 터미널 UI의 실시간 스피너(spinner, 로딩 중임을 나타내는 회전 아이콘) 업데이트를 가능하게 한답니다. 🔄

6단계: Post-Tool & 다음 턴 전이 (도구 실행 후 및 다음 턴 전환) (lines 1547-1727) ✨

도구 실행이 끝나면 여러 "부가 작업"들을 처리해요.

// 1. 스킬 디스커버리 소비 — 1단계에서 미리 가져오기 시작한 것을 여기서 마무리
if (pendingSkillPrefetch?.settledAt !== null) {
  const skillAttachments = await pendingSkillPrefetch.promise
}
// 2. 메모리 어태치먼트 소비 — 역시 미리 가져오기 한 것을 여기서 마무리
if (pendingMemoryPrefetch?.settledAt !== null) {
  const memoryAttachments = await pendingMemoryPrefetch.promise
}
// 3. 큐에 있는 명령어 처리 — 슬래시 명령어, 태스크 알림 등
const queuedCommands = getCommandsByMaxPriority(...)
// 4. MCP 서버 도구 새로고침
if (toolUseContext.options.refreshTools) {
  toolUseContext.options.tools = toolUseContext.options.refreshTools()
}

그리고 상태를 다음 턴으로 전환하죠.

// Continue Site: 다음 턴으로
state = {
  messages: [...messagesForQuery, ...assistantMessages, ...toolResults],
  toolUseContext: toolUseContextWithQueryTracking,
  autoCompactTracking: tracking,
  turnCount: nextTurnCount,
  transition: { reason: "next_turn" },
};
// while(true) 루프의 상단으로 돌아감

3.4 종료 이유: 루프가 끝나는 9가지 방법 🔚

에이전틱 루프는 다양한 이유로 종료될 수 있어요. 어떤 이유들이 있는지 살펴볼까요?

종료 이유의미발생 지점
completed정상적으로 완료 (도구 호출 없이 응답 종료)line 1264, 1357
blocking_limit하드 토큰 한도에 도달line 646
aborted_streaming스트리밍 중에 사용자가 중단 (Ctrl+C)line 1051
aborted_tools도구 실행 중에 사용자가 중단line 1515
prompt_too_long복구 시도 후에도 프롬프트 길이가 초과line 1175, 1182
image_error이미지 검증 실패 (크기 초과 등)line 977, 1175
model_error예상치 못한 모델 에러 발생line 996
hook_stoppedStop hook이 진행을 차단line 1520
max_turnsmaxTurns 파라미터로 설정된 최대 턴 수를 초과line 1711

3.5 QueryEngine.ts: 세션과 턴의 상위 관리자 🏛️

query.ts가 한 턴의 루프를 관리한다면, QueryEngine.ts(1,295줄)는 세션 전체를 관리하는 상위 관리자 역할을 해요.

class QueryEngine {
  mutableMessages: Message[]; // 전체 대화 기록
  permissionDenials: PermissionDenial[]; // 도구 권한 거부 기록
  totalUsage: Usage; // 누적 토큰 사용량
  readFileState: FileStateCache; // 파일 상태 캐시 (중복 읽기 방지)
  discoveredSkillNames: Set<string>; // 발견된 스킬 (턴마다 초기화)
  loadedNestedMemoryPaths: Set<string>; // 로드된 메모리 경로 (중복 방지)
}

submitMessage() 메서드가 사용자 입력을 받아서 query() 함수를 호출하고, 그 결과를 세션에 계속 쌓아나가는 역할을 해요.

// 사용량 누적
this.totalUsage = accumulateUsage(this.totalUsage, currentMessageUsage);

대화 기록(트랜스크립트)을 저장하는 방식에도 비대칭적인 전략이 적용되어 있어요.

// 사용자 메시지: 블로킹 저장 (--resume 복원에 필수)
await recordTranscript(userMessage);
// 어시스턴트 메시지: fire-and-forget (비동기, 논블로킹)
recordTranscript(assistantMessage); // await 없음

모든 메시지를 같은 우선순위로 저장하지 않는다는 의미예요. 사용자 메시지는 세션을 복원하는 데 필수적이므로 블로킹(즉시 저장) 방식으로 저장하고, 어시스턴트의 응답은 "저장은 하되, 저장이 완료될 때까지 기다리지는 않는다"는 비동기 전략을 사용하는 거죠. 이는 성능과 안정성 사이의 균형점을 찾은 설계라고 할 수 있어요. 👍


4. 메시지 압축: 컨텍스트 윈도우와의 정교한 전쟁 ⚔️

메시지 압축 계층

AI 에이전트 도구의 가장 큰 적은 바로 컨텍스트 윈도우(context window)의 한계예요. 대화가 길어지면 이전 내용이 잘려나가거나, API가 "413 Payload Too Large" 에러를 반환하는 문제가 발생하죠. Claude Code의 압축 시스템은 이 문제를 4단계 계층으로 해결하는데, 각 계층은 비용과 정보 손실 정도가 달라요. 핵심 원칙은 언제나 "가장 저렴한 것부터" 시작한다는 거예요! 💰

4.1 Snip Compact — 가장 저렴하고 가장 과격한 방법 ✂️

  • 비용: 무료 (API 호출 없음)
  • 정보 손실: 높음

오래된 내부 메시지를 통째로 제거하고 최근 대화 컨텍스트만 남기는 방식이에요. HISTORY_SNIP이라는 피처 플래그 뒤에 숨겨진 기능으로, 주로 헤드리스(headless, UI 없이 백그라운드에서 실행되는) 세션에서 사용돼요.

const snipResult = snipModule!.snipCompactIfNeeded(messagesForQuery)
messagesForQuery = snipResult.messages
snipTokensFreed = snipResult.tokensFreed

여기서 snipTokensFreed 값이 자동 압축 단계로 전달되는 것이 중요해요. 소스 코드 주석에 따르면:

// snipTokensFreed is plumbed to autocompact so its threshold check reflects
// what snip removed; tokenCountWithEstimation alone can't see it

Snip이 이미 상당한 양의 토큰을 줄였다면, Auto-Compact가 불필요하게 작동하는 것을 방지할 수 있도록 하는 거죠. 아주 효율적인 연동 방식이네요!

4.2 Microcompact (530줄) — 캐시를 존중하는 선택적 클리어 🧹

  • 비용: 무료 (API 호출 없음)
  • 정보 손실: 중간

이 방법은 도구 결과 중 특정 도구의 결과만 선택적으로 제거해요. 모든 도구 결과가 아니라, 특정 종류의 도구만 대상이 되죠.

// 클리어 대상: file_read, shell, grep, glob, web_search, web_fetch, file_edit, file_write
// 결과를 "[Old tool result content cleared]"로 교체
// 이미지/문서: 2,000 토큰으로 추정
// 텍스트: 대략적 토큰 카운트로 추정

이 계층의 진짜 핵심은 캐시 편집 블록 피닝(cache editing block pinning)이라는 기능이에요. CACHED_MICROCOMPACT 피처 플래그 뒤에 있는 이 기능은 API의 프롬프트 캐싱과 조화를 이루도록 설계되었답니다.

const pendingCacheEdits = feature("CACHED_MICROCOMPACT")
  ? microcompactResult.compactionInfo?.pendingCacheEdits
  : undefined;

이미 캐시된 도구 결과의 ID를 추적하고, 캐시 히트(cached hit, 이미 저장된 데이터를 다시 사용하는 것)가 유지되도록 관리해요. 프롬프트 캐싱에서 캐시 미스(cached miss, 캐시에 데이터가 없어서 새로 계산해야 하는 것) 한 번은 수만 토큰을 다시 계산해야 한다는 의미거든요. 캐시 히트율을 유지하면서 컨텍스트를 줄이는 것은 서로 상충하는 목표인데, 이 설계가 그 균형을 잡으려 노력한 흔적이 보여요. ⚖️

4.3 Context Collapse — 단계적 축소 📉

  • 비용: 낮음
  • 정보 손실: 중간

"단계적 축소(staged collapse)"라는 독특한 개념이에요. 전체 대화를 한 번에 압축하는 대신, 프리뷰 단계에서 어떤 메시지 블록을 축소할지 결정하고, 커밋 단계에서 실제로 축소하는 방식이죠.

소스 코드 주석이 이 설계의 핵심을 잘 설명하고 있어요.

// Nothing is yielded — the collapsed view is a read-time projection
// over the REPL's full history. Summary messages live in the collapse
// store, not the REPL array. This is what makes collapses persist
// across turns: projectView() replays the commit log on every entry.

축소된 뷰는 REPL(Read-Eval-Print Loop)의 전체 기록에 대한 읽기 시점 프로젝션(read-time projection)이에요. 원본 메시지는 그대로 남아있고, 축소된 결과만 별도의 저장소에 보관하는 거죠. Git의 스냅샷 저장 방식과 개념적으로 비슷해요. 원본은 건드리지 않으면서도 다른 "뷰"를 제공하는 것이랍니다.

이 접근 방식의 장점은 프롬프트 캐싱의 캐시 히트를 최대한 높일 수 있다는 점이에요. 원본 메시지가 변하지 않으므로, 캐시된 프리픽스(접두사)가 무효화되지 않기 때문이죠.

4.4 Auto-Compact (351줄) — 최후의 수단 💥

  • 비용: 높음 (Claude API 추가 호출 필요)
  • 정보 손실: 낮음 (AI가 요약하므로 핵심 내용은 보존됨)

전체 대화 기록을 Claude에게 보내서 요약을 요청하는 방식이에요. 이 기능이 작동하는 임계값(threshold) 로직은 명확하답니다.

function getAutoCompactThreshold(model: string): number {
  const effectiveContextWindow = getEffectiveContextWindowSize(model)
  return effectiveContextWindow - 13_000  // 13K 토큰 버퍼를 남김
}

컨텍스트 윈도우에서 13,000 토큰을 남겨두고, 그 이상으로 대화 길이가 차면 자동 압축이 발동하는 거죠.

여기에는 서킷 브레이커(circuit breaker) 기능도 있어요.

MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3;

자동 압축 자체가 실패하는 경우가 있을 수 있어요. 예를 들어, 대화가 너무 길어서 요약 요청 자체가 컨텍스트 한도를 넘는 경우죠. 이때 "압축 실패 → 재시도 → 또 실패"와 같은 무한 루프에 빠질 수 있는데, 3회 연속 실패하면 깔끔하게 포기하도록 설계되어 있어요. 방어적 프로그래밍의 교과서적인 예시라고 할 수 있겠네요! 👍

4.5 Full Compaction (1,705줄) — 마지막 카드 🃏

Auto-Compact가 실행될 때 내부적으로 호출하는 전체 압축 로직이에요.

  1. 이미지 스트리핑(stripping): 이미지와 문서를 [image] / [document] 같은 플레이스홀더로 교체하여 토큰을 대폭 절감해요.
  2. API 라운드 그룹핑: tool_usetool_result 쌍을 그룹으로 묶어 처리하여 의미적 단위를 보존해요.
  3. Thinking 블록 제거: (Anthropic 내부 빌드 전용) 추론 과정 블록을 압축 전에 제거해요.
  4. PTL(Prompt-Too-Long) 재시도: 압축 요청 자체가 한도를 넘으면, 가장 오래된 API 라운드 그룹부터 20%씩 잘라내요.
truncateHeadForPTLRetry(messages, ptlResponse) {
  // Prompt-too-long 문제가 해결될 때까지 가장 오래된 API 라운드 그룹을 잘라냄
  // 20%씩 그룹을 자르는 방식으로 폴백함
}

4.6 토큰 경고 상태 시스템 🚦

사용자에게 시각적인 피드백을 제공하는 4단계 경고 시스템도 있어요.

  • [정상]
    • ↓ 컨텍스트 윈도우 - 20K 토큰
  • [Warning] ← 노란색 경고 🟡
    • ↓ 컨텍스트 윈도우 - 20K 토큰
  • [Error] ← 주황색 경고 🟠
    • ↓ 컨텍스트 윈도우 - 13K 토큰
  • [AutoCompact] ← 자동 압축 발동
    • ↓ 컨텍스트 윈도우 - 3K 토큰
  • [BlockingLimit] ← 빨간색, 수동 압축만 가능 🔴

인사이트: 왜 4단계 압축일까요? 🤔

이 4단계 압축이 단순히 "점점 더 세게 압축"하는 것만은 아니에요. 각 단계는 서로 다른 트레이드오프를 가지고 있답니다.

  • Snip: 정보 손실은 크지만 캐시 히트에는 영향 없음
  • Microcompact: 선택적인 정보 손실이지만 캐시를 존중함
  • Context Collapse: 원본은 보존하면서 뷰만 축소함
  • Auto-Compact: 정보 손실은 최소이지만 API 호출 비용 발생

이는 "비용 최소화"와 "정보 보존 최대화"라는 두 가지 목표 사이에서 파레토 최적점(Pareto optimum)을 찾는 문제예요. 4개의 계층은 그 파레토 프론티어(Pareto frontier)의 서로 다른 점들을 나타내는 아주 영리한 설계라고 할 수 있습니다. 💡


5. 도구 시스템: 체계적으로 확장 가능한 스위스 아미 나이프 🇨🇭🔪

Claude Code가 실제로 무엇을 할 수 있는지는 바로 이 도구 시스템에 의해 결정돼요. tools.ts 파일을 보면, 도구들이 어떻게 등록되고 관리되는지 전체적인 그림을 볼 수 있답니다.

5.1 도구 인터페이스 🧩

모든 도구는 Tool.ts에 정의된 동일한 인터페이스를 따르도록 되어 있어요.

// ToolUseContext — 도구 실행에 필요한 모든 것이 여기 담깁니다
type ToolUseContext = {
  options: { tools: Tools; mainLoopModel: string; mcpClients: MCPServerConnection[]; maxBudgetUsd?: number; ... }
  abortController: AbortController
  readFileState: FileStateCache    // 중복 파일 읽기 방지
  getAppState(): AppState          // 애플리케이션 상태에 접근
  setAppState(f: (prev: AppState) => AppState): void
  // ... 40개 이상의 필드 (에이전트 ID, 권한 추적, 콘텐츠 교체 상태 등)
}

여기서 특히 주목할 만한 것은 ToolPermissionContext예요.

type ToolPermissionContext = DeepImmutable<{
  mode: PermissionMode;
  additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory>;
  alwaysAllowRules: ToolPermissionRulesBySource;
  alwaysDenyRules: ToolPermissionRulesBySource;
  alwaysAskRules: ToolPermissionRulesBySource;
  isBypassPermissionsModeAvailable: boolean;
  isAutoModeAvailable?: boolean;
  strippedDangerousRules?: ToolPermissionRulesBySource;
  shouldAvoidPermissionPrompts?: boolean; // 백그라운드 에이전트용
  awaitAutomatedChecksBeforeDialog?: boolean; // 코디네이터 워커용
  prePlanMode?: PermissionMode; // 플랜 모드 진입 전 모드 저장
}>;

ToolPermissionContextDeepImmutable이라는 것으로 감싸져 있어요. 즉, 권한 관련 정보는 읽기 전용이며, 실수로라도 수정될 수 없다는 것을 의미하죠. 아주 중요한 보안 조치라고 할 수 있습니다.

5.2 도구 등록의 3계층 구조 🏛️

getAllBaseTools() 함수를 살펴보면, Claude Code의 도구들이 세 가지 계층으로 나뉘어 있음을 알 수 있어요.

  1. 항상 활성화: BashTool, FileReadTool, FileEditTool, WebSearchTool, AgentTool 등 20여 개의 기본 도구들이 여기에 속해요. 이 도구들이 Claude Code의 핵심적인 기능을 담당하죠.
  2. 조건부 활성화: 환경이나 설정에 따라 켜지거나 꺼지는 도구들이에요. 예를 들어, GlobToolGrepTool은 Anthropic 내부 빌드에서는 비활성화돼요. 왜냐하면 Bun 바이너리에 bfs/ugrep 같은 기능이 내장되어 있어서 별도의 도구가 필요 없기 때문이죠. PowerShellTool은 Windows에서만, LSPTool은 특정 환경변수를 명시적으로 켜야만 사용할 수 있어요.
  3. 피처 플래그 기반 (미출시): feature() 함수로 관리되는 도구들로, 아직 공개되지 않은 기능들이에요. WebBrowserTool, WorkflowTool, SleepTool, PushNotificationTool 등 15개 이상이 여기에 속하죠. 이 기능들은 섹션 7에서 더 자세히 다룰 예정이에요.

흥미로운 점은 USER_TYPE === 'ant'로 분기되는 Anthropic 내부 전용 도구들이 있다는 거예요.

const REPLTool =
  process.env.USER_TYPE === "ant"
    ? require("./tools/REPLTool/REPLTool.js").REPLTool
    : null;

REPLTool, ConfigTool, TungstenTool, SuggestBackgroundPRTool 같은 도구들은 외부 사용자에게는 존재하지 않아요. 이는 Anthropic 엔지니어들이 내부적으로 사용하는 특별한 도구 세트가 따로 있다는 것을 의미하죠. 특히 TungstenTool은 이름만으로는 용도를 알 수 없지만, 텅스텐(고밀도 금속)이라는 이름에서 "무거운 작업"을 처리하는 도구일 것으로 추정해 볼 수 있어요. 🧐

이 3계층 구조가 왜 중요할까요? AI 에이전트 도구에서 "어떤 능력을 줄 것인가"는 핵심적인 설계 결정이에요. 도구가 너무 많으면 시스템 프롬프트가 비대해져서 토큰 비용이 증가하고, 너무 적으면 에이전트의 능력이 제한되죠. Claude Code는 이 문제를 빌드 타임 제거 + 조건부 활성화 + 피처 플래그의 3단계로 해결하고 있어요. Cursor나 Devin 같은 경쟁 제품들도 비슷한 도구 확장 문제에 직면할 텐데, 이런 계층적 접근 방식은 좋은 참고 자료가 될 거예요.

5.3 도구 풀 조립: 캐시 안정성까지 고려한 설계 🏗️

assembleToolPool() 함수에서 빌트인 도구(내장 도구)와 MCP 도구(Model Context Protocol 도구)를 합치는 로직이 있는데, 여기서 프롬프트 캐시의 안정성을 위한 흥미로운 처리가 보여요.

export function assembleToolPool(permissionContext, mcpTools): Tools {
  const builtInTools = getTools(permissionContext);
  const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext);
  // 프롬프트-캐시 안정성을 위해 각 부분을 정렬하며, 빌트인 도구는 연속적인 접두사로 유지합니다.
  // 서버의 claude_code_system_cache_policy는 마지막 접두사 일치 빌트인 도구 뒤에 전역 캐시 중단점을 배치합니다.
  // 평면 정렬을 하면 MCP 도구가 빌트인 도구 사이에 끼어들어 기존 빌트인 도구 사이에
  // MCP 도구가 정렬될 때마다 모든 다운스트림 캐시 키를 무효화합니다.
  const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name);
  return uniqBy(
    [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
    "name",
  );
}

빌트인 도구와 MCP 도구를 각각 따로 정렬한 뒤 합쳐요. 왜 전체를 한 번에 정렬하지 않을까요? 🧐 서버의 claude_code_system_cache_policy가 빌트인 도구의 마지막 항목 뒤에 캐시 브레이크포인트(cache breakpoint)를 두기 때문이에요. 만약 전체를 평면적으로 정렬하면, MCP 도구가 빌트인 도구 사이에 끼어들 수 있고, 이로 인해 MCP 도구가 추가되거나 제거될 때마다 모든 후속 캐시 키가 무효화될 수 있어요.

이러한 수준의 캐시 최적화는 실제 프로덕션 환경에서의 비용 경험에서 나온 것으로 보입니다. 개발 과정에서 토큰 비용을 얼마나 중요하게 생각하는지 엿볼 수 있는 부분이죠. 💸

5.4 동적 도구 검색 (Tool Search) 🔍

tool-search-2025-10-16이라는 베타 기능이에요. 도구의 수가 수십 개가 되면, 시스템 프롬프트(LLM에 전달되는 초기 지시문)만으로도 상당한 토큰을 차지하게 돼요. 이 기능은 모든 도구를 처음부터 Claude에게 보여주는 대신, 필요할 때 검색해서 로드하는 방식을 사용해요.

// tools.ts:247-249
// 도구 검색이 활성화될 수 있을 때 ToolSearchTool 포함 (낙관적 확인)
// 실제 도구 지연 결정은 claude.ts의 요청 시점에 이루어집니다.
...(isToolSearchEnabledOptimistic() ? [ToolSearchTool] : []),

"optimistic check"라는 표현이 눈에 띄는데요. 도구를 등록하는 시점에서는 일단 포함될 수 있다고 낙관적으로 판단하고, 실제로 도구를 지연(defer)시킬지 말지는 API 요청 시점에 결정해요. 이건 마치 게으른(lazy) 로딩의 LLM 버전이라고 할 수 있겠네요! 🐌


6. 다층 보안: 양파를 까보면 🧅

보안 레이어

"AI 에이전트가 내 파일 시스템에 접근한다"는 문장은 보안 연구자들을 깜짝 놀라게 하기에 충분하죠. 😱 Claude Code가 이 문제를 어떻게 다루는지 살펴보면, 마치 양파처럼 겹겹이 쌓인 8개의 다층 방어막을 발견할 수 있어요.

6.1 레이어 1: 빌드 타임 게이트 — 코드가 존재하지 않는 보안 🚫

// tools.ts:117-119
const WebBrowserTool = feature("WEB_BROWSER_TOOL")
  ? require("./tools/WebBrowserTool/WebBrowserTool.js").WebBrowserTool
  : null;

여기서 사용된 feature() 함수는 빌드 타임(build time)에 평가돼요. Bun 번들러(bundler)가 false로 판단되는 분기(require())의 코드를 아예 제거해 버리는 거죠. 즉, 외부로 배포되는 빌드에는 Anthropic 내부 전용 도구의 코드가 물리적으로 존재하지 않는답니다. 😮 그래서 런타임(runtime)에 환경변수를 바꿔서 기능을 활성화하는 것은 불가능해요. 바이너리(실행 파일)에 코드 자체가 없으니까요.

동일한 코드베이스에서 "내부용"과 "외부용" 빌드를 분기하면서도, 런타임 분기문의 보안 위험을 피하는 아주 기발한 접근 방식이에요. 물론, 이번에 유출된 건 바로 그 빌드 타임에 제거되었어야 할 코드가 source map에 남아있었기 때문이라는 아이러니한 상황이 발생했죠. 😅

USER_TYPE 환경변수도 같은 방식으로 사용돼요.

// tools.ts:16-19
const REPLTool =
  process.env.USER_TYPE === "ant"
    ? require("./tools/REPLTool/REPLTool.js").REPLTool
    : null;

6.2 레이어 2: 피처 플래그 — 서버 측 킬 스위치 🚨

빌드가 완료된 후에도, 서버 측에서 특정 기능을 제어할 수 있는 방법이 있어요. GrowthBook 기반의 tengu_ 접두사 플래그들이 이 역할을 한답니다.

플래그역할
tengu_amber_quartz_disabled음성 모드 킬 스위치 (음성 모드를 강제로 비활성화)
tengu_bypass_permissions_disabled권한 우회 모드 킬 스위치 (권한 검사를 건너뛰는 모드를 비활성화)
tengu_auto_mode_config.enabled자동 모드 서킷 브레이커 (자동 모드 작동을 중단시킬 수 있음)
tengu_ccr_bridge원격 제어 자격 확인 (클라우드 기반 원격 제어 기능 사용 여부 확인)
tengu_sessions_elevated_auth_enforcement신뢰 디바이스 토큰 요구 (특정 세션에서 더 높은 인증 요구)

만약 보안 사고가 발생하면, 서버에서 이 킬 스위치(kill switch)를 즉시 작동시켜 문제가 되는 기능을 비활성화할 수 있어요. 클라이언트 업데이트 없이 기능을 끌 수 있다는 점은 인시던트 대응 관점에서 매우 중요하죠.

Anthropic 내부에서는 환경변수로 이를 재정의(override)할 수도 있어요.

// CLAUDE_INTERNAL_FC_OVERRIDES (Anthropic 전용)
// '{"my_feature": true, "my_config": {"key": "val"}}'

6.3 레이어 3: 설정 기반 규칙 — 8개 소스의 우선순위 📜

권한 규칙은 다양한 출처에서 오며, 각 출처에는 우선순위가 있어요.

type PermissionRule = {
  source: PermissionRuleSource; // 8개 소스 중 하나
  ruleBehavior: "allow" | "deny" | "ask"; // 허용, 거부, 또는 물어보기
  ruleValue: {
    toolName: string; // "Bash", "FileEdit" 등 도구 이름
    ruleContent?: string; // 패턴, 예: "python:*"
  };
};

규칙 소스의 우선순위는 다음과 같아요. (높은 순서대로)

  1. userSettings — 사용자 홈 디렉토리의 설정 파일 (~/.claude/settings.json)
  2. projectSettings — 현재 프로젝트 디렉토리의 설정 파일 (.claude/settings.json)
  3. localSettings — 로컬 환경에서만 적용되는 설정 파일 (.claude/local.json)
  4. flagSettings — GrowthBook 피처 플래그를 통한 설정
  5. policySettings — 조직 수준의 정책 (엔터프라이즈 환경용)
  6. cliArg — 명령줄 인수를 통해 전달된 설정
  7. command — 런타임에 동적으로 업데이트되는 규칙
  8. session — 현재 세션에서만 유효한 인메모리 규칙

6.4 레이어 4: Transcript Classifier — AI가 AI를 감시합니다 🕵️‍♀️

yoloClassifier.ts — 52KB짜리 파일이에요. (파일 이름이 "YOLO"인데 52KB라니, Anthropic 엔지니어들의 유머 감각이 엿보이네요. 😄)

자동 모드(auto)에서는 사용자에게 매번 "이거 해도 될까요?"라고 묻는 대신, Claude API를 한 번 더 호출해서 도구 사용의 안전성을 판단해요.

  1. 도구 사용 요청 발생
  2. 화이트리스트(whitelist) 체크
    • FileRead, Grep, Glob, Tasks 같은 읽기 전용 도구는 분류기를 건너뛰고 자동으로 허용돼요.
  3. Classifier API 호출 → "허용" 또는 "거부" 결정
  4. 거부 추적:
    • 3회 연속 거부 → 프롬프팅 모드로 폴백 (사용자에게 직접 물어봄)
    • 총 20회 거부 → 프롬프팅 모드로 폴백

거부 추적의 폴백 메커니즘이 중요해요. 분류기가 너무 보수적으로 작동하면 사용자 경험이 나빠질 수 있으니, 일정 횟수 이상 거부하면 "차라리 사용자에게 물어보자"고 전환하는 거죠.

디버깅용으로 CLAUDE_CODE_DUMP_AUTO_MODE=1 환경변수를 설정하면 분류기의 요청/응답을 /tmp/claude-code/auto-mode/에 JSON 파일로 저장해요. Claude Code의 자동 모드가 왜 특정 작업을 거부하는지 궁금할 때 유용한 팁이 될 거예요!

만약 분류기가 API 에러를 반환하면 어떻게 될까요? 프롬프팅으로 폴백합니다. 즉, 자동으로 거부하지 않아요. "판단할 수 없으면 사용자에게 묻는다"는 fail-open 전략인데요. 보안에서 fail-open은 보통 위험하지만, 여기서는 "사용자가 직접 판단"이라는 폴백이므로 합리적인 선택이라고 볼 수 있어요.

6.5 레이어 5: 위험 패턴 감지 ⚠️

특정 명령어 패턴을 위험하다고 판단하여 차단해요.

DANGEROUS_BASH_PATTERNS = [
  "python",
  "node",
  "ruby",
  "perl",
  "bash",
  "sh",
  "zsh",
  "ksh",
  "exec",
  "eval",
  "source",
  "curl",
  "wget",
  "nc",
  "ncat",
  "socat",
  "dd",
  "xxd",
  "openssl",
  "ssh",
  "scp",
  "sftp",
  "sudo",
  "su",
  "chroot",
  "unshare",
  "docker",
  "podman",
  "chmod",
  "chown",
  "chgrp",
  "umask",
  "mount",
  "umount",
];

자동 모드에서 python:*이나 인터프리터 와일드카드(예: python -*)를 허용 규칙으로 설정하려고 하면 차단돼요.

isDangerousBashPermission(rule) {
  // 도구 수준에서 content 없이 allow (모든 bash 허용) → 위험
  // "python:*", "python*", "python -*" → 위험
  // 인터프리터 프리픽스 + 와일드카드 → 위험
}

인터프리터를 통한 임의 코드 실행은 Bash 샌드박싱을 우회할 수 있기 때문에, python -c "import os; os.system('rm -rf /')"와 같은 공격을 원천적으로 봉쇄하려는 목적이죠. 아주 영리한 방어막입니다!

6.6 레이어 6: 파일 시스템 권한 검증 (62KB) 📂

가장 큰 권한 파일(62KB)은 오직 파일 경로 검증만 담당해요.

  • 절대 경로 정규화: 상대 경로의 .이나 .. 같은 특수 문자를 올바르게 처리해요.
  • 심볼릭 링크 탈출 방지: 허용된 디렉토리 안의 심볼릭 링크(다른 파일을 가리키는 링크)가 허용 범위를 벗어난 곳을 가리키는 공격을 차단해요.
  • Glob 패턴 안전 확장: /**/*와 같은 패턴이 예상치 못한 경로를 포함하지 않도록 안전하게 확장해요.
  • CWD(현재 작업 디렉토리) 전용 모드 vs 전체 접근 모드: acceptEdits 모드에서는 현재 디렉토리 내에서만 파일 접근을 허용해요.
  • 스크래치패드 지원: 코디네이터(Coordinator) 워커의 공유 디렉토리 접근을 허용해요 (tengu_scratch).
  • Windows/POSIX 경로 처리: 크로스 플랫폼 환경에서도 올바르게 작동하도록 Windows 및 POSIX(Unix/Linux) 경로를 모두 처리해요.

6.7 레이어 7: Trust Dialog (신뢰 대화상자) 🤝

Claude Code를 처음 실행할 때 나타나는 보안 대화상자예요. 다음 항목들을 검토하고 사용자의 동의를 받죠.

  • 프로젝트 범위의 MCP 서버 설정
  • 커스텀 Hook 설정
  • Bash 권한 설정
  • API 키 헬퍼 (API 키 관리 보조 기능)
  • AWS/GCP 명령 접근
  • OTEL(OpenTelemetry) 헤더 설정

이 Trust Dialog를 통과하지 못하면 파일 및 파일 시스템 관련 작업이 모두 차단돼요.

6.8 레이어 8: Bypass Permissions Kill Switch (권한 우회 킬 스위치) 🔪

최후의 수단이에요. GrowthBook 서버에서 tengu_bypass_permissions_disabled 플래그를 활성화하면:

// bypassPermissionsKillswitch.ts
// 사용자가 bypass 모드에 진입하는 것 자체를 차단
// 이전 모드로 강제 복귀
// 진단 메시지 표시

사용자가 권한 우회 모드에 진입하는 것을 아예 막아버리고, 이전 모드로 강제 복귀시키는 강력한 기능입니다.

인사이트: 보안 모델의 설계 철학 💡

이 8개의 레이어를 관통하는 일관된 원칙이 있어요.

  1. 기본적으로 거부 (Deny by default) — 명시적인 허용 규칙이 있어야만 작동해요.
  2. 거부가 아닌 질문 (Fail to prompting, not to deny) — 판단할 수 없는 상황에서는 사용자에게 물어보지, 무조건 거부하지 않아요.
  3. 심층 방어 (Defense in depth) — 한 레이어가 뚫려도 다음 레이어가 방어해준답니다.
  4. 서버 측 킬 스위치 (Server-side kill switch) — 클라이언트 업데이트 없이도 즉시 기능을 비활성화할 수 있어요.
  5. 빌드 타임 제거 (Build-time elimination) — 코드가 아예 존재하지 않으면 해당 취약점도 존재할 수 없다는 개념이죠.

7. 미출시 기능: Anthropic의 로드맵이 유출되었어요! 🗺️

피처 플래그 뒤에 숨어 있던 미출시 기능들이 이번 유출을 통해 세상에 드러났어요. 아마 경쟁사들에게는 가장 "의도치 않은 선물"이 되었을 거예요. 🎁

7.1 Voice Mode — 음성으로 코딩하기 🗣️💻

// voice/voiceModeEnabled.ts
export function isVoiceModeEnabled(): boolean {
  return hasVoiceAuth() && isVoiceGrowthBookEnabled();
}
export function hasVoiceAuth(): boolean {
  // OAuth 전용 (Claude.ai 계정 필요)
  // API 키/Bedrock/Vertex로는 사용 불가
  // voice_stream 엔드포인트 사용
}
export function isVoiceGrowthBookEnabled(): boolean {
  return feature("VOICE_MODE")
    ? !getFeatureValue_CACHED_MAY_BE_STALE(
        "tengu_amber_quartz_disabled",
        false,
      )
    : false;
}

이 코드를 통해 몇 가지 중요한 사실을 알 수 있어요.

  • 전용 voice_stream API 엔드포인트가 존재해요. 일반 Messages API와는 별도로 사용되죠.
  • OAuth 인증 전용이에요. API 키나 AWS Bedrock, Google Cloud Vertex AI 같은 서드파티 클라우드 사용자는 접근할 수 없어요. Claude.ai 계정이 필요하다는 뜻이죠.
  • GrowthBook 킬 스위치(tengu_amber_quartz_disabled)가 있어서, 서버에서 문제가 발생하면 즉시 음성 모드를 비활성화할 수 있어요.
  • 캐싱 전략으로 _CACHED_MAY_BE_STALE이 사용돼요. 이는 실시간으로 서버에 플래그 값을 묻는 것이 아니라, 캐시된 플래그 값을 사용한다는 의미예요. 음성 모드 활성화 여부를 매번 서버에 물어보지 않아서 성능을 최적화한 것이죠.

7.2 Web Browser Tool — 진짜 브라우저 자동화 🌐🤖

const WebBrowserTool = feature("WEB_BROWSER_TOOL")
  ? require("./tools/WebBrowserTool/WebBrowserTool.js").WebBrowserTool
  : null;

현재 WebFetchTool은 단순히 정적인 HTML 페이지만 가져오는 기능이에요. 하지만 이 WebBrowserTool은 Bun의 WebView API를 활용한 실제 브라우저 자동화 도구로 추정됩니다. 즉, JavaScript 렌더링이 필요한 SPA(Single Page Application) 페이지와도 상호작용할 수 있다는 의미죠. OpenAI의 Computer Use(이전에는 Web Browsing) 기능의 CLI(Command Line Interface) 버전이라고 볼 수 있겠네요!

7.3 Coordinator Mode — 멀티 에이전트 오케스트레이션 (19KB) 🧑‍🤝‍🧑

가장 야심찬 미출시 기능 중 하나예요.

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

Coordinator는 직접 코드를 작성하지 않아요. 대신 여러 워커 에이전트(worker agents)를 생성하고 작업을 분배하는, 일종의 메타 오케스트레이터(meta orchestrator) 역할을 하죠.

  • 워커는 AgentTool을 통해 생성돼요.
  • 결과는 <task-notification> XML 블록 형태로 도착해요.
  • SendMessage로 워커에게 후속 지시를 보낼 수 있어요.
  • TaskStop으로 워커를 종료할 수 있고요.

Coordinator가 사용할 수 있는 도구는 아주 엄격하게 제한돼요.

COORDINATOR_MODE_ALLOWED_TOOLS = new Set([
  "AgentTool", // 워커 생성
  "TaskStop", // 워커 종료
  "SendMessage", // 워커와 통신
  "SyntheticOutput", // 출력 생성
]);

Coordinator는 Bash 명령어를 실행할 수 없어요. 파일도 읽을 수 없고요. 오직 워커들을 관리하는 역할만 수행하죠. 이는 권한 분리(principle of least privilege) 원칙을 극단적으로 적용한 사례라고 볼 수 있어요. 오케스트레이터가 직접 도구를 실행할 수 있다면, 그것 자체가 단일 장애점(single point of failure)이 될 수 있기 때문이죠.

공유 스크래치패드 (tengu_scratch GrowthBook 게이트) 기능도 있어요.

워커들 간의 정보 공유를 위한 디렉토리인데, 일반적인 파일 시스템 권한 검사를 우회해요. 워커들이 현재 작업 디렉토리(CWD) 밖의 스크래치패드에 접근할 수 있어야 하니까요.

이는 사실상 AI 에이전트의 마이크로서비스 아키텍처라고 할 수 있어요. 하나의 Claude가 여러 Claude를 오케스트레이션하는 구조이며, 각 워커는 격리된 컨텍스트에서 독립적으로 작업을 수행하는 거죠.

7.4 Kairos — 선제적 어시스턴트 ⏰💡

그리스어로 "적절한 때"를 의미하는 Kairos는 Claude가 먼저 행동하는 모드예요.

피처 플래그도구기능
KAIROSSendUserFileTool사용자에게 파일을 선제적으로 전송
KAIROS || KAIROS_PUSH_NOTIFICATIONPushNotificationTool모바일/데스크톱 푸시 알림
KAIROS_GITHUB_WEBHOOKSSubscribePRToolGitHub PR 웹훅 구독
PROACTIVE || KAIROSSleepTool백그라운드 대기 (타이머)
KAIROS_CHANNELS(미상)멀티 채널 통합
KAIROS_BRIEF(미상)체크포인트/상태 업데이트

이 시나리오를 그려보면 이래요. GitHub PR에 새로운 리뷰 코멘트가 달리면 SubscribePRTool이 이를 감지하고, CI(Continuous Integration) 결과를 확인한 뒤, PushNotificationTool로 "PR #123에 리뷰가 달렸고, CI가 실패했습니다. 이 부분을 수정하면 될 것 같습니다"와 같은 알림을 보내는 거죠. SleepTool로 주기적으로 깨어나서 상태를 점검할 수도 있고요.

이는 사용자가 세션을 열지 않아도 Claude가 스스로 일을 처리하는 구조예요. 코딩 어시스턴트의 역할을 넘어 자율 소프트웨어 엔지니어링 에이전트로의 전환을 의미한답니다. 🚀

7.5 Agent Triggers & Monitoring (에이전트 트리거 및 모니터링) 🕰️📊

const cronTools = feature("AGENT_TRIGGERS")
  ? [CronCreateTool, CronDeleteTool, CronListTool]
  : [];
const RemoteTriggerTool = feature("AGENT_TRIGGERS_REMOTE")
  ? require("./tools/RemoteTriggerTool/RemoteTriggerTool.js")
      .RemoteTriggerTool
  : null;
const MonitorTool = feature("MONITOR_TOOL")
  ? require("./tools/MonitorTool/MonitorTool.js").MonitorTool
  : null;

팀메이트(teammate)가 생성한 크론(cron, 특정 시간에 자동으로 작업을 실행하는 스케줄러)은 해당 에이전트의 agentId로 태그되고, 그 에이전트의 메시지 큐로 라우팅돼요. 이는 장기 실행 에이전트가 자기만의 스케줄을 관리할 수 있다는 것을 의미하죠.

7.6 UDS Inbox — 멀티 디바이스 메시징 📱💻

const ListPeersTool = feature("UDS_INBOX")
  ? require("./tools/ListPeersTool/ListPeersTool.js").ListPeersTool
  : null;

"UDS"는 Unified Device Stack으로 추정돼요. 에이전트가 "피어"(다른 연결된 디바이스나 인스턴스)를 조회하고, bridge://other:// 스키마를 통해 메시지를 라우팅할 수 있는 기능이죠.

이는 노트북의 Claude와 데스크톱의 Claude가 서로 통신하는 미래를 상상하게 합니다. 아직 먼 이야기일 수도 있지만, 이미 그 기반은 마련되어 있다는 뜻이죠. 🌉

7.7 Workflow Scripts (워크플로우 스크립트) 📝⚙️

const WorkflowTool = feature("WORKFLOW_SCRIPTS")
  ? (() => {
      require("./tools/WorkflowTool/bundled/index.js").initBundledWorkflows();
      return require("./tools/WorkflowTool/WorkflowTool.js").WorkflowTool;
    })()
  : null;

번들된 워크플로우(미리 만들어진 자동화 스크립트)가 있고, 이를 초기화하는 시스템도 있어요. 서브 에이전트 내부에서는 워크플로우의 재귀 실행이 차단됩니다(ALL_AGENT_DISALLOWED_TOOLS에 포함됨).

인사이트: 미출시 기능이 말해주는 Anthropic의 방향성 🧭

이러한 미출시 기능들을 종합해보면, Anthropic의 전략이 아주 명확하게 드러나요.

  1. CLI(Command Line Interface) → 플랫폼: 단순한 터미널 도구에서 벗어나, 여러 디바이스와 에이전트가 연동되는 복합적인 플랫폼으로 확장하려 하고 있어요.
  2. 반응적 → 선제적: 사용자 명령을 기다리는 수동적인 역할에서, 스스로 모니터링하고 알림을 보내는 자율적인 존재로 변화하고 있어요.
  3. 텍스트 → 멀티모달: 키보드 입력 중심에서 음성, 그리고 브라우저 자동화와 같은 다양한 입력 및 상호작용 방식으로 확장하고 있죠.
  4. 단일 → 오케스트레이션: 하나의 에이전트가 모든 것을 처리하는 방식이 아니라, 코디네이터가 여러 에이전트 무리(스웜)를 관리하는 분산 시스템으로 진화하고 있어요.

7.8 Bridge — 원격 제어 시스템 (33개 이상 파일) 🌉

Bridge 시스템은 무려 33개 이상의 파일로 구성된 거대한 서브시스템이에요. Claude.ai 웹사이트에서 로컬 머신에 있는 Claude Code를 제어하는 "원격 제어(Remote Control)" 기능의 백엔드 역할을 담당하죠.

연결 흐름 🔗

  1. 사용자가 OAuth를 통해 claude.ai에 로그인해요.
  2. CCR(Claude Cloud Runtime) API가 구독 상태와 GrowthBook 플래그(tengu_ccr_bridge)를 확인해요.
  3. 로컬 Claude Code가 environment_idenvironment_secret을 획득해요.
  4. 인증된 터널이 WebSocket을 통해 설정돼요.
  5. 브라우저가 Bridge API를 통해 로컬 도구를 실행하고, 그 결과를 다시 받아요.

보안 티어 🛡️

Bridge 시스템은 보안 수준에 따라 두 가지 티어를 사용해요.

티어인증 요구사항용도
StandardOAuth 토큰일반적인 원격 세션
ElevatedOAuth + Trusted Device Token (JWT)민감한 작업이 포함된 세션

Elevated 티어에서는 X-Trusted-Device-Token 헤더를 통해 디바이스의 신뢰성을 추가로 검증해요.

// bridge/trustedDevice.ts
// 물리적 디바이스에 바인딩된 JWT
// "이 요청이 실제로 등록된 장치에서 온 것"을 보장

세션 격리 🔒

각 원격 세션은 Git worktree로 격리돼요. 브라우저에서 두 개의 탭으로 같은 프로젝트에 접속하더라도, 각각 독립된 작업 디렉토리에서 작업을 수행하는 거죠. 이는 세션 간의 병합 충돌(merge conflict)을 원천적으로 봉쇄하는 아주 영리한 접근 방식이에요.

세션의 라이프사이클은 sessionRunner.ts가 관리하며, JWT로 서명된 WorkSecret을 사용하여 세션 핸드오프(handover) 보안을 유지합니다.

인사이트: 원격 제어의 설계 교훈 🧐

Bridge 시스템에서 주목할 만한 점은 바로 세션 격리 전략이에요. Git worktree를 세션 격리 단위로 사용한 것은 기존 인프라(Git)를 활용하여 파일 시스템 수준의 격리를 달성하는 실용적인 선택이라고 할 수 있어요. 컨테이너나 가상 머신(VM) 수준의 격리보다 오버헤드(추가적인 자원 소모)가 낮으면서도, 각 세션이 독립된 작업 디렉토리를 가질 수 있게 해준답니다.

또한, 2티어 인증 구조(Standard/Elevated)도 배울 점이 많아요. 모든 원격 작업에 최고 수준의 인증을 요구하면 사용자 경험이 나빠지고, 너무 낮은 수준만 요구하면 보안이 약해지죠. 작업의 민감도에 따라 인증 수준을 다르게 하는 이 접근 방식은, 보안과 편의성 사이의 균형을 잡는 아주 현실적인 방법이라고 볼 수 있어요. 👍


8. 이 아키텍처가 업계에 의미하는 것 💡

이번 유출된 코드를 통해 Claude Code의 내부를 살펴본 것은, AI 에이전트 도구의 실제 운영 아키텍처를 들여다볼 수 있는 매우 드문 기회였어요. 몇 가지 핵심적인 교훈을 정리해볼게요.

에이전틱 시스템은 생각보다 훨씬 복잡해요! 🤯

"LLM API를 호출하고 도구를 실행하면 끝!"이라는 건 사실 데모 수준의 아주 단순한 이해일 뿐이에요. 실제 프로덕션 환경의 에이전틱 시스템은 다음과 같은 복잡한 요소들을 필요로 한답니다.

  • 컨텍스트 관리: 4단계의 정교한 압축 계층, 캐시 안정성을 위한 도구 정렬 전략, 서버와 클라이언트 간의 예산 동기화 등.
  • 에러 복구: 비용을 인식하는 캐스케이드(단계적 복구), 감소 수익 감지 기능, 서킷 브레이커(오류 확산 방지) 등.
  • 보안: 8개 레이어에 걸친 다층 방어, ML 기반의 자동 판정 시스템, 빌드 타임 코드 제거 기법 등.
  • 성능: 스트리밍 중 병렬로 도구를 실행하는 기능, 지연된 동시 프리페치(미리 가져오기), 비대칭적인 트랜스크립트(대화 기록) 저장 방식 등.

Claude Code의 4,600개 파일은 이러한 복잡성을 증명하는 확실한 증거예요. OpenAI의 Codex, Cursor, Devin 같은 경쟁 제품들도 내부적으로는 이와 비슷한 수준의 복잡성을 가지고 있을 거예요. 단지 그 코드들이 공개된 적이 없을 뿐이죠.

비용이 아키텍처를 결정합니다 💰

Claude Code 아키텍처를 관통하는 가장 중요한 원칙 중 하나는 바로 비용 인식(cost-awareness)이에요.

  • 에러 복구는 항상 무료 옵션부터 먼저 시도해요.
  • 메시지 압축도 API 호출이 없는 방법부터 적용하죠.
  • 감소 수익이 감지되면 불필요한 토큰 낭비를 멈춰요.
  • 도구 정렬 방식도 프롬프트 캐시의 히트율을 최적화하기 위한 것이에요.
  • 도구 검색은 필요할 때만 로드해서 자원을 아껴요.

API 호출 한 번이 곧 비용으로 직결되는 LLM 시스템에서는 "가장 저렴한 방법부터"라는 원칙이 단순히 기술적인 우아함이 아니라, 경제적인 생존 전략이 된답니다. 에이전틱 시스템을 만드는 모든 팀이 결국 이 문제에 직면하게 될 거예요.

"오픈소스"의 새로운 정의가 필요해요 🤔

이번 Claude Code 사례는 AI 시대의 "오픈소스"라는 단어의 의미에 대해 깊은 질문을 던지고 있어요. 공식 저장소에는 플러그인 인터페이스(279개 파일)만 공개되어 있고, 핵심 엔진(4,600개 이상 파일)은 상용 라이선스 뒤에 숨겨져 있었죠. 과연 이걸 "오픈소스"라고 부를 수 있을까요?

이 문제는 비단 Anthropic만의 것이 아니에요. 많은 AI 제품들이 "오픈"이라는 라벨을 달고 확장 인터페이스만 공개한 채, 핵심 기술은 비공개로 유지하고 있답니다. 사용자와 개발자 커뮤니티가 이러한 구분을 명확히 인식하는 것이 매우 중요할 거예요.


9. 마무리: 유출이 말해주는 것 🎓

npm source map이라는 어처구니없는 경로로 세상에 공개된 Claude Code의 내부는, 단순한 API 래퍼가 아니라 4,600개 파일에 걸친 풀스택 에이전트 플랫폼이었다는 사실이 밝혀졌습니다.

8개 레이어의 다층 보안, 4단계 메시지 압축 계층, 감소 수익 감지, 비용 인식을 통한 에러 복구, 그리고 캐시 안정성을 위한 도구 정렬 — 이러한 세부 사항들은 단순한 "API 래퍼"에서는 찾아볼 수 없는, 프로덕션 레벨의 정교한 엔지니어링을 보여줍니다.

미출시 기능들은 Anthropic의 미래 방향성을 명확히 드러내고 있습니다. 음성 모드, 웹 브라우저 자동화, 멀티 에이전트 코디네이터, 그리고 선제적인 Kairos 모드는 "코딩 어시스턴트"를 넘어 "자율 소프트웨어 엔지니어링 플랫폼"으로의 전환이 이미 코드 레벨에서 활발하게 진행 중임을 시사합니다.

여기에는 아이러니가 있습니다. 빌드 타임 데드코드 제거라는 세련된 보안 메커니즘을 구현해놓고, 같은 빌드 파이프라인에서 source map 파일을 제거하는 것을 누락했습니다. 8겹의 양파 껍질처럼 사용자의 파일 시스템을 철저히 보호하면서도, 정작 자기 소스 코드는 npm에 노출시켜버린 것이죠. 🤷‍♀️

이 분석에서 소개한 패턴들, 예를 들어 비용 인식 에러 복구, 다단계 컨텍스트 압축, 빌드 타임 피처 게이트, 감소 수익 감지 등은 Claude Code에만 유효한 것이 아닙니다. 에이전틱 AI 시스템을 구축하는 모든 개발자에게 귀중한 참고 자료가 될 수 있습니다. 특히 "가장 저렴한 복구부터 시도한다"는 원칙은 API 비용이 핵심 변수인 모든 LLM 애플리케이션에 반드시 적용되어야 할 교훈입니다.

어쩌면 이것이 소프트웨어 엔지니어링의 본질일지도 모릅니다. 아무리 정교하게 설계된 시스템이라도 가장 단순한 실수에 의해 무너질 수 있다는 것을요. Anthropic의 엔지니어들이 구축한 것은 분명 인상적인 에이전틱 아키텍처입니다. 다만 다음번에는 .npmignore 파일에 *.map을 추가하는 것을 잊지 않기를 바랍니다. 😉


이 글은 유출된 소스코드의 기술적 분석을 목적으로 하며, 소스코드의 재배포나 사용을 권장하지 않습니다. 모든 코드의 저작권은 Anthropic PBC에 있습니다.

함께 읽으면 좋은 글

Harvest엔지니어링 리더십 · AI한국어

에이전트 시대의 ‘스킬 파일’ 표준화가 왜 모든 걸 바꾸는가

이 영상은 2025년 10월에 등장한 Skills(스킬) 이 이제는 “개인용 프롬프트 모음”이 아니라, 조직 전체의 AI 인프라로 바뀌었다는 흐름을 정리합니다. 핵심은 스킬을 사람이 읽기 좋은 문서가 아니라 에이전트가 호출하기 좋은 계약(Contract) 으로 설계해야 한다는 점이에요. 그...

2026년 3월 31일더 읽기
HarvestAI한국어

에이전트가 ‘코딩’하고, 연구가 ‘루프’를 돌기 시작한 시대: 안드레이 카파시 대담 요약

안드레이 카파시는 최근 몇 달 사이 코딩 에이전트의 도약으로 인해, 사람이 직접 코드를 치기보다 “에이전트에게 의도를 전달하는 일”이 핵심이 됐다고 말합니다. 그는 이 흐름이 오토리서치(AutoResearch)처럼 “실험–학습–최적화”를 사람이 거의 개입하지 않고 굴리는 자율 연구 루프로...

2026년 3월 22일더 읽기
Harvest엔지니어링 리더십 · AI한국어

Software 3.0 시대, 조직의 생산성을 끌어올리는 AI 하네스 구축하기

이 글은 개발팀 내에서 개인의 역량에 크게 의존하고 있는 AI(LLM) 활용 방식을 조직 전체의 체계적인 시스템으로 발전시켜야 한다는 핵심적인 메시지를 담고 있습니다. 특히 Claude Code의 플러그인과 마켓플레이스 생태계를 단순한 확장 도구가 아닌, 팀의 업무 방식과 지식을 코드로 만...

2026년 3월 8일더 읽기