<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Harness-Engineering on ICE-ICE-BEAR-BLOG</title><link>https://ice-ice-bear.github.io/ko/categories/harness-engineering/</link><description>Recent content in Harness-Engineering on ICE-ICE-BEAR-BLOG</description><generator>Hugo -- gohugo.io</generator><language>ko</language><lastBuildDate>Mon, 06 Apr 2026 00:00:00 +0900</lastBuildDate><atom:link href="https://ice-ice-bear.github.io/ko/categories/harness-engineering/index.xml" rel="self" type="application/rss+xml"/><item><title>Claude Code 하네스 해부학 #1 — 진입점에서 응답까지, 요청 하나의 여정</title><link>https://ice-ice-bear.github.io/ko/posts/2026-04-06-harness-anatomy-1/</link><pubDate>Mon, 06 Apr 2026 00:00:00 +0900</pubDate><guid>https://ice-ice-bear.github.io/ko/posts/2026-04-06-harness-anatomy-1/</guid><description>&lt;img src="https://ice-ice-bear.github.io/" alt="Featured image of post Claude Code 하네스 해부학 #1 — 진입점에서 응답까지, 요청 하나의 여정" /&gt;&lt;h2 id="개요"&gt;개요
&lt;/h2&gt;&lt;p&gt;Claude Code의 소스 구조를 27세션에 걸쳐 체계적으로 해부하는 시리즈의 첫 번째 글이다. 이 포스트에서는 사용자가 터미널에 &amp;ldquo;hello&amp;quot;를 입력했을 때, 응답이 화면에 출력되기까지 거치는 &lt;strong&gt;11개 TypeScript 파일의 전체 콜스택&lt;/strong&gt;을 추적한다.&lt;/p&gt;
&lt;h2 id="분석-대상-11개-핵심-파일"&gt;분석 대상: 11개 핵심 파일
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;#&lt;/th&gt;
 &lt;th&gt;경로&lt;/th&gt;
 &lt;th&gt;줄 수&lt;/th&gt;
 &lt;th&gt;역할&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;entrypoints/cli.tsx&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;302&lt;/td&gt;
 &lt;td&gt;CLI 부트스트랩, 인자 파싱, 모드 라우팅&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;main.tsx&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;4,683&lt;/td&gt;
 &lt;td&gt;메인 REPL 컴포넌트, Commander 설정&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;commands.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;754&lt;/td&gt;
 &lt;td&gt;커맨드 레지스트리&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;context.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;189&lt;/td&gt;
 &lt;td&gt;시스템 프롬프트 조립, CLAUDE.md 주입&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;QueryEngine.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1,295&lt;/td&gt;
 &lt;td&gt;세션 관리, SDK 인터페이스&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;6&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;query.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1,729&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;핵심 턴 루프&lt;/strong&gt; — API + 도구 실행&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;7&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;services/api/client.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;389&lt;/td&gt;
 &lt;td&gt;HTTP 클라이언트, 4개 프로바이더 라우팅&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;8&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;services/api/claude.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;3,419&lt;/td&gt;
 &lt;td&gt;Messages API 래퍼, SSE 스트리밍, 재시도&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;9&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;services/tools/toolOrchestration.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;188&lt;/td&gt;
 &lt;td&gt;동시성 파티셔닝&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;10&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;services/tools/StreamingToolExecutor.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;530&lt;/td&gt;
 &lt;td&gt;스트리밍 중 도구 실행&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;11&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;services/tools/toolExecution.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1,745&lt;/td&gt;
 &lt;td&gt;도구 디스패치, 권한 검사&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;총 &lt;strong&gt;15,223줄&lt;/strong&gt;을 추적한다.&lt;/p&gt;
&lt;h2 id="1-진입과-부트스트랩-clitsx---maintsx"&gt;1. 진입과 부트스트랩: cli.tsx -&amp;gt; main.tsx
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;cli.tsx&lt;/code&gt;는 302줄에 불과하지만, 놀라울 정도로 많은 &lt;strong&gt;패스트-패스(fast-path)&lt;/strong&gt; 분기가 존재한다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cli.tsx:37 --version -&amp;gt; 즉시 출력, import 0개
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cli.tsx:53 --dump-system -&amp;gt; 최소 import
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cli.tsx:100 --daemon-worker -&amp;gt; 워커 전용 경로
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cli.tsx:112 remote-control -&amp;gt; 브릿지 모드
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cli.tsx:185 ps/logs/attach -&amp;gt; 백그라운드 세션
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cli.tsx:293 기본 경로 -&amp;gt; main.tsx 동적 import
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;설계 의도&lt;/strong&gt;: &lt;code&gt;--version&lt;/code&gt; 하나를 위해 &lt;code&gt;main.tsx&lt;/code&gt;의 4,683줄을 로딩하지 않겠다는 것이다. CLI 도구의 체감 응답성에 직접 영향을 미치는 최적화다.&lt;/p&gt;
&lt;p&gt;기본 경로에서는 동적 import로 &lt;code&gt;main.tsx&lt;/code&gt;를 로드한다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// cli.tsx:293-297
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt;: &lt;span class="kt"&gt;cliMain&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kr"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;../main.js&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cliMain&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;main.tsx&lt;/code&gt;가 4,683줄인 이유는 다음을 모두 포함하기 때문이다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;사이드 이펙트 import&lt;/strong&gt; (1-209행): &lt;code&gt;profileCheckpoint&lt;/code&gt;, &lt;code&gt;startMdmRawRead&lt;/code&gt;, &lt;code&gt;startKeychainPrefetch&lt;/code&gt; — 모듈 평가 시점에 병렬 서브프로세스를 시작하여 약 65ms의 macOS keychain 읽기를 숨긴다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commander 설정&lt;/strong&gt; (585행~): CLI 인자 파싱, 10+개 모드별 분기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;React/Ink REPL 렌더링&lt;/strong&gt;: 터미널 UI 마운트&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;헤드리스 경로&lt;/strong&gt; (&lt;code&gt;-p&lt;/code&gt;/&lt;code&gt;--print&lt;/code&gt;): UI 없이 &lt;code&gt;QueryEngine&lt;/code&gt; 직접 사용&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="2-프롬프트-조립-contextts의-dual-memoize"&gt;2. 프롬프트 조립: context.ts의 dual-memoize
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;context.ts&lt;/code&gt;는 189줄의 작은 파일이지만 시스템 프롬프트의 동적 부분을 전담한다. 두 개의 메모이즈된 함수가 핵심이다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;getSystemContext()&lt;/code&gt;&lt;/strong&gt; (context.ts:116): git 상태(branch, status, 최근 커밋)를 수집&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;getUserContext()&lt;/code&gt;&lt;/strong&gt; (context.ts:155): CLAUDE.md 파일들을 탐색/파싱&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;왜 분리했는가?&lt;/strong&gt; Anthropic Messages API의 프롬프트 캐싱 전략과 직결된다. 시스템 프롬프트와 사용자 컨텍스트의 캐시 수명이 다르므로 &lt;code&gt;cache_control&lt;/code&gt;을 서로 다르게 적용해야 한다. &lt;code&gt;memoize&lt;/code&gt;로 감싸서 세션 내 한 번만 계산한다.&lt;/p&gt;
&lt;p&gt;context.ts:170-176에서 &lt;code&gt;setCachedClaudeMdContent()&lt;/code&gt;를 호출하는 것은 &lt;strong&gt;순환 의존성을 끊기 위한 장치&lt;/strong&gt;다 — yoloClassifier가 CLAUDE.md 내용을 필요로 하지만, 직접 import하면 permissions -&amp;gt; yoloClassifier -&amp;gt; claudemd -&amp;gt; permissions 순환이 발생한다.&lt;/p&gt;
&lt;h2 id="3-asyncgenerator-체인-아키텍처의-척추"&gt;3. AsyncGenerator 체인: 아키텍처의 척추
&lt;/h2&gt;&lt;p&gt;Claude Code의 전체 데이터 플로우는 &lt;code&gt;AsyncGenerator&lt;/code&gt; 체인으로 구성된다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;QueryEngine.submitMessage()* -&amp;gt; query()* -&amp;gt; queryLoop()* -&amp;gt; queryModelWithStreaming()*
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;모든 핵심 함수가 &lt;code&gt;async function*&lt;/code&gt;이다. 이는 단순한 구현 선택이 아니라 &lt;strong&gt;아키텍처적 결정&lt;/strong&gt;이다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Backpressure&lt;/strong&gt;: 소비자가 느리면 생산자도 대기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;취소&lt;/strong&gt;: AbortController와 결합하여 즉각적인 취소 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;합성&lt;/strong&gt;: &lt;code&gt;yield*&lt;/code&gt;로 제너레이터 체인을 자연스럽게 연결&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;상태 관리&lt;/strong&gt;: 루프 내 로컬 변수가 턴 간 상태를 자연스럽게 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;QueryEngine.submitMessage()&lt;/code&gt; (QueryEngine.ts:209)의 시그니처를 보면:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;submitMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;ContentBlockParam&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;uuid?&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;isMeta?&lt;/span&gt;: &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AsyncGenerator&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;SDKMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;SDK 모드에서 각 메시지는 &lt;strong&gt;yield로 스트리밍&lt;/strong&gt;되며, Node.js의 backpressure가 자연스럽게 구현된다.&lt;/p&gt;
&lt;h2 id="4-핵심-턴-루프-queryts의-whiletrue"&gt;4. 핵심 턴 루프: query.ts의 while(true)
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;query.ts&lt;/code&gt;(1,729줄)의 &lt;code&gt;queryLoop()&lt;/code&gt;가 실제 API+도구 루프다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// query.ts:307
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 1. queryModelWithStreaming() 호출 -&amp;gt; SSE 스트림
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 2. 스트리밍 이벤트를 yield
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 3. 도구 호출 감지 -&amp;gt; runTools()/StreamingToolExecutor
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 4. 도구 결과를 메시지에 추가
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 5. stop_reason == &amp;#34;end_turn&amp;#34; -&amp;gt; break
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// stop_reason == &amp;#34;tool_use&amp;#34; -&amp;gt; continue
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;State&lt;/code&gt; 타입(query.ts:204)이 중요하다. &lt;code&gt;messages&lt;/code&gt;, &lt;code&gt;toolUseContext&lt;/code&gt;, &lt;code&gt;autoCompactTracking&lt;/code&gt;, &lt;code&gt;maxOutputTokensRecoveryCount&lt;/code&gt; 등 루프 상태를 명시적 레코드로 관리하여, continue 사이트에서 한 번에 갱신한다.&lt;/p&gt;
&lt;h2 id="5-api-통신-4개-프로바이더와-캐싱"&gt;5. API 통신: 4개 프로바이더와 캐싱
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;client.ts:88&lt;/code&gt;의 &lt;code&gt;getAnthropicClient()&lt;/code&gt;는 4가지 프로바이더를 지원한다:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;프로바이더&lt;/th&gt;
 &lt;th&gt;SDK&lt;/th&gt;
 &lt;th&gt;동적 import 이유&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Anthropic Direct&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;Anthropic&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;기본, 즉시 로딩&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;AWS Bedrock&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;AnthropicBedrock&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;AWS SDK 수 MB&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Azure Foundry&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;AnthropicFoundry&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Azure Identity 수 MB&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;GCP Vertex&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;AnthropicVertex&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Google Auth 수 MB&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;claude.ts&lt;/code&gt;(3,419줄)의 핵심 함수 체인:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;queryModelWithStreaming() (claude.ts:752)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; queryModel()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; withRetry()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; anthropic.beta.messages.stream() (SDK 호출)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;캐싱 전략은 &lt;code&gt;getCacheControl()&lt;/code&gt; (claude.ts:358)이 1시간 TTL 여부를 사용자 유형, 피처 플래그, 쿼리 소스에 따라 결정한다.&lt;/p&gt;
&lt;h2 id="6-도구-오케스트레이션-3-tier-동시성"&gt;6. 도구 오케스트레이션: 3-tier 동시성
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 TC["도구 호출 배열&amp;lt;br/&amp;gt;[ReadFile, ReadFile, Bash, ReadFile]"]
 P["partitionToolCalls()&amp;lt;br/&amp;gt;toolOrchestration.ts:91"]
 B1["Batch 1&amp;lt;br/&amp;gt;ReadFile + ReadFile&amp;lt;br/&amp;gt;isConcurrencySafe=true"]
 B2["Batch 2&amp;lt;br/&amp;gt;Bash&amp;lt;br/&amp;gt;isConcurrencySafe=false"]
 B3["Batch 3&amp;lt;br/&amp;gt;ReadFile&amp;lt;br/&amp;gt;isConcurrencySafe=true"]
 PAR["Promise.all()&amp;lt;br/&amp;gt;최대 10개 동시"]
 SEQ["순차 실행"]
 PAR2["Promise.all()"]

 TC --&gt; P
 P --&gt; B1
 P --&gt; B2
 P --&gt; B3
 B1 --&gt; PAR
 B2 --&gt; SEQ
 B3 --&gt; PAR2

 style B1 fill:#e8f5e9
 style B2 fill:#ffebee
 style B3 fill:#e8f5e9&lt;/pre&gt;&lt;p&gt;&lt;code&gt;StreamingToolExecutor&lt;/code&gt;(530줄)는 이 배치 파티셔닝을 &lt;strong&gt;스트리밍 컨텍스트&lt;/strong&gt;로 확장한다. API 응답이 스트리밍되는 도중에 도구 호출을 감지하면 즉시 실행을 시작한다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;addTool()&lt;/code&gt; (StreamingToolExecutor.ts:76) — 큐에 추가&lt;/li&gt;
&lt;li&gt;&lt;code&gt;processQueue()&lt;/code&gt; (StreamingToolExecutor.ts:140) — 동시성 확인 후 즉시 실행&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getRemainingResults()&lt;/code&gt; (StreamingToolExecutor.ts:453) — 모든 도구 완료 대기&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;에러 전파 규칙&lt;/strong&gt;: Bash 에러만 형제 도구를 취소한다 (&lt;code&gt;siblingAbortController&lt;/code&gt;). Read/WebFetch 에러는 다른 도구에 영향을 주지 않는다. 이는 Bash 명령 간의 암묵적 의존성(mkdir 실패 -&amp;gt; 후속 명령 무의미)을 반영한 설계다.&lt;/p&gt;
&lt;h2 id="전체-데이터-플로우"&gt;전체 데이터 플로우
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;sequenceDiagram
 participant User as 사용자
 participant CLI as cli.tsx
 participant Main as main.tsx
 participant QE as QueryEngine
 participant Query as query.ts
 participant Claude as claude.ts
 participant API as Anthropic API
 participant Tools as toolOrchestration
 participant Exec as toolExecution

 User-&gt;&gt;CLI: "hello" 입력
 CLI-&gt;&gt;Main: dynamic import
 Main-&gt;&gt;QE: new QueryEngine()
 QE-&gt;&gt;Query: query()
 Query-&gt;&gt;Claude: queryModelWithStreaming()
 Claude-&gt;&gt;API: anthropic.beta.messages.stream()
 API--&gt;&gt;Claude: SSE 스트림

 alt stop_reason == end_turn
 Claude--&gt;&gt;User: 응답 출력
 else stop_reason == tool_use
 Claude--&gt;&gt;Query: tool_use blocks
 Query-&gt;&gt;Tools: partitionToolCalls()
 Tools-&gt;&gt;Exec: runToolUse()
 Exec-&gt;&gt;Exec: canUseTool() + tool.call()
 Exec--&gt;&gt;Query: 도구 결과
 Note over Query: while(true) 다음 반복
 end&lt;/pre&gt;&lt;h2 id="rust-갭-지도-미리보기"&gt;Rust 갭 지도 미리보기
&lt;/h2&gt;&lt;p&gt;동일한 요청을 Rust 포트에서 추적한 결과, &lt;strong&gt;31개 갭&lt;/strong&gt;을 식별했다:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;우선순위&lt;/th&gt;
 &lt;th&gt;갭 수&lt;/th&gt;
 &lt;th&gt;핵심 예시&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;P0 (치명적)&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;동기 ApiClient, StreamingToolExecutor 부재&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P1 (높음)&lt;/td&gt;
 &lt;td&gt;6&lt;/td&gt;
 &lt;td&gt;3-tier 동시성, 프롬프트 캐싱, Agent 도구&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P2 (보통)&lt;/td&gt;
 &lt;td&gt;7&lt;/td&gt;
 &lt;td&gt;멀티 프로바이더, 노력 제어, 샌드박스&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;구현 완료&lt;/td&gt;
 &lt;td&gt;11&lt;/td&gt;
 &lt;td&gt;자동 압축, SSE 파서, OAuth, 설정 로딩&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;구현 완료율: 36% (11/31)&lt;/strong&gt;. 다음 포스트에서 이 갭들의 핵심인 대화 루프를 깊이 파고든다.&lt;/p&gt;
&lt;h2 id="인사이트"&gt;인사이트
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AsyncGenerator가 아키텍처의 척추다&lt;/strong&gt; — 단순한 구현 기법이 아니라 backpressure, 취소, 합성을 한 번에 해결하는 설계 결정이다. Rust에서는 &lt;code&gt;Stream&lt;/code&gt; trait이 대응하지만 &lt;code&gt;yield*&lt;/code&gt; 합성의 인체공학이 크게 다르다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;main.tsx 4,683줄은 기술 부채다&lt;/strong&gt; — Commander 설정, React 컴포넌트, 상태 관리가 한 파일에 혼재. 역사적 성장의 결과로, 모듈 분리의 기회가 된다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;도구 동시성이 단순하지 않다&lt;/strong&gt; — &amp;ldquo;전부 병렬&amp;rdquo; 또는 &amp;ldquo;전부 직렬&amp;quot;이 아닌 3계층 모델(읽기 배치, 쓰기 순차, Bash 형제 취소)은 실전 에이전트 하네스의 핵심 설계 요소다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;다음 포스트: &lt;a class="link" href="https://ice-ice-bear.github.io/posts/2026-04-06-harness-anatomy-2/" &gt;#2 — 대화 루프의 심장, StreamingToolExecutor와 7개의 continue&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Claude Code 하네스 해부학 #2 — 대화 루프의 심장, StreamingToolExecutor와 7개의 continue</title><link>https://ice-ice-bear.github.io/ko/posts/2026-04-06-harness-anatomy-2/</link><pubDate>Mon, 06 Apr 2026 00:00:00 +0900</pubDate><guid>https://ice-ice-bear.github.io/ko/posts/2026-04-06-harness-anatomy-2/</guid><description>&lt;img src="https://ice-ice-bear.github.io/" alt="Featured image of post Claude Code 하네스 해부학 #2 — 대화 루프의 심장, StreamingToolExecutor와 7개의 continue" /&gt;&lt;h2 id="개요"&gt;개요
&lt;/h2&gt;&lt;p&gt;시리즈 첫 번째 글에서 &amp;ldquo;hello&amp;rdquo; 한마디가 11개 파일을 관통하는 여정을 추적했다. 이번 포스트에서는 그 여정의 심장부인 &lt;code&gt;query.ts&lt;/code&gt; 1,729줄의 &lt;code&gt;while(true)&lt;/code&gt; 루프를 완전 해부한다. 7가지 &lt;code&gt;continue&lt;/code&gt; 경로가 만드는 탄력적 실행 모델, &lt;code&gt;StreamingToolExecutor&lt;/code&gt;의 4단계 상태 머신, 그리고 &lt;code&gt;partitionToolCalls()&lt;/code&gt;의 3계층 동시성 모델을 분석한 뒤, Rust 프로토타입에서 이를 어떻게 재현했는지 대조한다.&lt;/p&gt;
&lt;h2 id="분석-대상-10개-핵심-파일"&gt;분석 대상: 10개 핵심 파일
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;#&lt;/th&gt;
 &lt;th&gt;경로&lt;/th&gt;
 &lt;th&gt;줄 수&lt;/th&gt;
 &lt;th&gt;역할&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;query/config.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;46&lt;/td&gt;
 &lt;td&gt;이뮤터블 런타임 게이트 스냅샷&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;query/deps.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;40&lt;/td&gt;
 &lt;td&gt;테스트 가능한 I/O 경계 (DI)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;query/tokenBudget.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;93&lt;/td&gt;
 &lt;td&gt;토큰 예산 관리, 자동 연속/중단 결정&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;query/stopHooks.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;473&lt;/td&gt;
 &lt;td&gt;Stop/TaskCompleted/TeammateIdle 훅&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;query.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1,729&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;핵심&lt;/strong&gt; &amp;ndash; while(true) 턴 루프&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;6&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;QueryEngine.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1,295&lt;/td&gt;
 &lt;td&gt;세션 래퍼, SDK 인터페이스&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;7&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;toolOrchestration.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;188&lt;/td&gt;
 &lt;td&gt;도구 파티셔닝 + 동시성 제어&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;8&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;StreamingToolExecutor.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;530&lt;/td&gt;
 &lt;td&gt;SSE 중 도구 파이프라이닝&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;9&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;toolExecution.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1,745&lt;/td&gt;
 &lt;td&gt;도구 디스패치, 권한 검사&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;10&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;toolHooks.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;650&lt;/td&gt;
 &lt;td&gt;Pre/PostToolUse 훅 파이프라인&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;총 &lt;strong&gt;6,789줄&lt;/strong&gt;의 핵심 오케스트레이션 코드를 해부한다.&lt;/p&gt;
&lt;h2 id="1-queryloop의-7가지-continue-경로"&gt;1. queryLoop()의 7가지 continue 경로
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;query.ts&lt;/code&gt;의 &lt;code&gt;queryLoop()&lt;/code&gt; 함수(query.ts:241)는 단순한 API 호출 루프가 아니다. 7가지 &lt;code&gt;continue&lt;/code&gt; 사유를 가진 &lt;strong&gt;탄력적 실행기&lt;/strong&gt;다. 각 경로는 고유한 장애 시나리오를 처리한다:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;사유&lt;/th&gt;
 &lt;th&gt;행&lt;/th&gt;
 &lt;th&gt;설명&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;collapse_drain_retry&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1114&lt;/td&gt;
 &lt;td&gt;컨텍스트 축소 드레인 후 재시도&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;reactive_compact_retry&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1162&lt;/td&gt;
 &lt;td&gt;반응형 압축 후 재시도 (413 복구)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;max_output_tokens_escalate&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1219&lt;/td&gt;
 &lt;td&gt;8k -&amp;gt; 64k 토큰 에스컬레이션&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;max_output_tokens_recovery&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1248&lt;/td&gt;
 &lt;td&gt;&amp;ldquo;이어서 작성하라&amp;rdquo; 넛지 메시지 주입&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;stop_hook_blocking&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1303&lt;/td&gt;
 &lt;td&gt;Stop 훅이 블로킹 에러 반환&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;token_budget_continuation&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1337&lt;/td&gt;
 &lt;td&gt;토큰 예산 미달로 계속&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;next_turn&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1725&lt;/td&gt;
 &lt;td&gt;도구 실행 완료 후 다음 턴&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;State 타입이 핵심이다&lt;/strong&gt; (query.ts:204-217). 루프 상태를 10개 필드의 레코드로 관리한다. 왜 개별 변수가 아닌 레코드인가? &lt;code&gt;continue&lt;/code&gt; 사이트가 7곳이며, 각각 &lt;code&gt;state = { ... }&lt;/code&gt;으로 한 번에 갱신한다. 9개 변수를 개별 할당하면 하나를 빠뜨리는 실수가 발생하기 쉽다. &lt;strong&gt;레코드 갱신은 타입 시스템이 누락을 잡아준다.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="루프-한-반복의-전체-흐름"&gt;루프 한 반복의 전체 흐름
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. 전처리 (365-447): snip 압축, 마이크로 컴팩트, 컨텍스트 축소
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. 자동 압축 (454-543): 성공 시 메시지 교체 후 continue
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. 블로킹 한도 검사 (628-648): 토큰 임계값 초과 시 즉시 종료
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4. API 스트리밍 (654-863): SSE 이벤트를 for await로 소비
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;5. 도구 없는 종료 경로 (1062-1357): 413 복구, max_output 복구, stop 훅
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;6. 도구 있는 계속 경로 (1360-1728): 나머지 도구 실행 -&amp;gt; next_turn
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="2-streamingtoolexecutor의-4단계-상태-머신"&gt;2. StreamingToolExecutor의 4단계 상태 머신
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;StreamingToolExecutor.ts&lt;/code&gt;(530줄)는 Claude Code에서 가장 정교한 동시성 패턴이다. 핵심 아이디어: &lt;strong&gt;API 응답이 아직 스트리밍되는 동안 이미 완성된 도구 호출의 실행을 시작&lt;/strong&gt;한다.&lt;/p&gt;
&lt;p&gt;모델이 &lt;code&gt;[ReadFile(&amp;quot;a.ts&amp;quot;), ReadFile(&amp;quot;b.ts&amp;quot;), Bash(&amp;quot;make test&amp;quot;)]&lt;/code&gt;를 한 번에 호출하는 경우, 파이프라이닝 없이는 세 도구 블록이 모두 도착한 후에야 실행이 시작된다. 파이프라이닝에서는 &lt;code&gt;ReadFile(&amp;quot;a.ts&amp;quot;)&lt;/code&gt; 블록이 완성되는 즉시 파일 읽기가 시작된다.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;stateDiagram-v2
 [*] --&gt; queued: addTool()
 queued --&gt; executing: processQueue()&amp;lt;br/&amp;gt;canExecuteTool() == true
 queued --&gt; completed: 사전 취소&amp;lt;br/&amp;gt;getAbortReason() != null

 executing --&gt; completed: 도구 실행 완료&amp;lt;br/&amp;gt;또는 sibling abort

 completed --&gt; yielded: getCompletedResults()&amp;lt;br/&amp;gt;순서대로 yield

 yielded --&gt; [*]

 note right of queued
 processQueue()는 addTool()
 + 이전 도구 완료 시 자동 트리거
 end note

 note right of completed
 Bash 에러 시
 siblingAbortController.abort()
 형제 도구만 취소
 end note&lt;/pre&gt;&lt;h3 id="동시성-결정-로직-canexecutetool-line-129"&gt;동시성 결정 로직 (canExecuteTool, line 129)
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;실행 가능 조건:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 현재 실행 중인 도구가 없음 (executingTools.length === 0)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 또는: 이 도구가 concurrencySafe이고 실행 중인 모든 도구도 concurrencySafe
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;read-only 도구끼리는 병렬 실행이 가능하지만, write 도구가 하나라도 있으면 그 도구가 끝날 때까지 다음 도구는 대기한다.&lt;/p&gt;
&lt;h3 id="siblingabortcontroller--계층적-취소"&gt;siblingAbortController &amp;ndash; 계층적 취소
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;siblingAbortController&lt;/code&gt;(line 46-61)는 &lt;code&gt;toolUseContext.abortController&lt;/code&gt;의 자식이다. Bash 도구가 에러를 발생시키면 &lt;code&gt;siblingAbortController.abort('sibling_error')&lt;/code&gt;를 호출하여 &lt;strong&gt;형제 도구만 취소&lt;/strong&gt;한다. 부모 컨트롤러는 영향을 받지 않으므로 전체 쿼리는 계속 실행된다.&lt;/p&gt;
&lt;p&gt;왜 Bash 에러만 형제를 취소하는가? &lt;code&gt;mkdir -p dir &amp;amp;&amp;amp; cd dir &amp;amp;&amp;amp; make&lt;/code&gt;에서 mkdir이 실패하면 후속 명령은 무의미하다. ReadFile이나 WebFetch 실패는 독립적이므로 다른 도구에 영향을 주지 않아야 한다.&lt;/p&gt;
&lt;h2 id="3-partitiontoolcalls--3계층-동시성-모델"&gt;3. partitionToolCalls &amp;ndash; 3계층 동시성 모델
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;toolOrchestration.ts&lt;/code&gt;(188줄)는 도구 실행의 동시성 모델 전체를 정의한다.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 TC["도구 호출 배열&amp;lt;br/&amp;gt;[ReadFile, ReadFile, Bash, ReadFile]"]
 P["partitionToolCalls()&amp;lt;br/&amp;gt;toolOrchestration.ts:91"]
 B1["Batch 1&amp;lt;br/&amp;gt;ReadFile + ReadFile&amp;lt;br/&amp;gt;isConcurrencySafe=true"]
 B2["Batch 2&amp;lt;br/&amp;gt;Bash&amp;lt;br/&amp;gt;isConcurrencySafe=false"]
 B3["Batch 3&amp;lt;br/&amp;gt;ReadFile&amp;lt;br/&amp;gt;isConcurrencySafe=true"]
 PAR["Promise.all()&amp;lt;br/&amp;gt;최대 10개 동시"]
 SEQ["순차 실행"]
 PAR2["Promise.all()"]

 TC --&gt; P
 P --&gt; B1
 P --&gt; B2
 P --&gt; B3
 B1 --&gt; PAR
 B2 --&gt; SEQ
 B3 --&gt; PAR2

 style B1 fill:#e8f5e9
 style B2 fill:#ffebee
 style B3 fill:#e8f5e9&lt;/pre&gt;&lt;p&gt;규칙은 단순하다: 연속된 &lt;code&gt;isConcurrencySafe&lt;/code&gt; 도구는 하나의 배치로 묶고, 그렇지 않은 도구는 각각 독립 배치가 된다. 이 결정은 &lt;strong&gt;도구 정의 자체에서&lt;/strong&gt; 나온다 &amp;ndash; &lt;code&gt;tool.isConcurrencySafe(parsedInput)&lt;/code&gt; 호출로 결정된다. 같은 도구라도 입력에 따라 동시성 안전성이 달라질 수 있다.&lt;/p&gt;
&lt;h3 id="컨텍스트-수정자와-경쟁-조건"&gt;컨텍스트 수정자와 경쟁 조건
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;왜 배치 완료 후 순서대로 적용하는가?&lt;/strong&gt; 병렬 실행 중 컨텍스트 수정자를 즉시 적용하면 경쟁 조건이 발생한다. A가 먼저 완료되어 컨텍스트를 수정하면, 아직 실행 중인 B는 수정 전 컨텍스트로 시작했지만 수정 후 컨텍스트를 보게 된다. 배치 완료 후 원래 도구 순서대로 적용하면 결정론적 결과를 보장한다 (toolOrchestration.ts:54-62).&lt;/p&gt;
&lt;h2 id="4-도구-실행-파이프라인과-훅"&gt;4. 도구 실행 파이프라인과 훅
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;toolExecution.ts&lt;/code&gt;(1,745줄)의 &lt;code&gt;runToolUse()&lt;/code&gt;(line 337)가 개별 도구 호출의 전체 생명주기를 관리한다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;runToolUse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;진입점&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mf"&gt;1.&lt;/span&gt; &lt;span class="n"&gt;findToolByName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="n"&gt;deprecated&lt;/span&gt; &lt;span class="err"&gt;별칭으로&lt;/span&gt; &lt;span class="err"&gt;재시도&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;345&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;356&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mf"&gt;2.&lt;/span&gt; &lt;span class="n"&gt;abort&lt;/span&gt; &lt;span class="err"&gt;체크&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="err"&gt;이미&lt;/span&gt; &lt;span class="err"&gt;취소되었으면&lt;/span&gt; &lt;span class="n"&gt;CANCEL_MESSAGE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;415&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mf"&gt;3.&lt;/span&gt; &lt;span class="n"&gt;streamedCheckPermissionsAndCallTool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="err"&gt;권한&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;실행&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;훅&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;455&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;checkPermissionsAndCallTool&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Zod&lt;/span&gt; &lt;span class="err"&gt;스키마로&lt;/span&gt; &lt;span class="err"&gt;입력&lt;/span&gt; &lt;span class="err"&gt;유효성&lt;/span&gt; &lt;span class="err"&gt;검사&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;615&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="k"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;validateInput&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;커스텀&lt;/span&gt; &lt;span class="err"&gt;검증&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;683&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="err"&gt;투기적&lt;/span&gt; &lt;span class="err"&gt;분류기&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bash&lt;/span&gt; &lt;span class="err"&gt;전용&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;740&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;runPreToolUseHooks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;resolveHookPermissionDecision&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;921&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="k"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;실제&lt;/span&gt; &lt;span class="err"&gt;실행&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1207&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;runPostToolUseHooks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;결과&lt;/span&gt; &lt;span class="err"&gt;변환&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="resolvehookpermissiondecision의-핵심-불변식"&gt;resolveHookPermissionDecision의 핵심 불변식
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;resolveHookPermissionDecision()&lt;/code&gt;(toolHooks.ts:332)에서 &lt;strong&gt;훅의 &lt;code&gt;allow&lt;/code&gt;가 settings.json의 deny/ask 규칙을 바이패스하지 않는다&lt;/strong&gt; (toolHooks.ts:373). 훅이 allow해도 &lt;code&gt;checkRuleBasedPermissions()&lt;/code&gt;를 통과해야 한다. 이것은 &amp;ldquo;훅은 자동화 도우미이지 보안 우회가 아니다&amp;quot;라는 설계 원칙을 반영한다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;훅 결과가 allow일 때:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; checkRuleBasedPermissions() 호출
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; null이면 통과 (규칙 없음)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; deny이면 규칙이 훅을 오버라이드
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; ask이면 사용자 프롬프트 필요
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="5-rust-대조--152줄-vs-1729줄"&gt;5. Rust 대조 &amp;ndash; 152줄 vs 1,729줄
&lt;/h2&gt;&lt;p&gt;Rust의 &lt;code&gt;ConversationRuntime::run_turn()&lt;/code&gt;은 &lt;strong&gt;152줄의 단일 &lt;code&gt;loop {}&lt;/code&gt;&lt;/strong&gt; (conversation.rs:183-272)로 구성된다. TS의 7가지 continue 경로 중 Rust에 존재하는 것은 &lt;code&gt;next_turn&lt;/code&gt;(도구 실행 완료 후 다음 턴) 딱 하나뿐이다.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;TS continue 사유&lt;/th&gt;
 &lt;th&gt;Rust 상태&lt;/th&gt;
 &lt;th&gt;이유&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;collapse_drain_retry&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;미구현&lt;/td&gt;
 &lt;td&gt;컨텍스트 축소 없음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;reactive_compact_retry&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;미구현&lt;/td&gt;
 &lt;td&gt;413 복구 없음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;max_output_tokens_escalate&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;미구현&lt;/td&gt;
 &lt;td&gt;8k-&amp;gt;64k 에스컬레이션 없음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;max_output_tokens_recovery&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;미구현&lt;/td&gt;
 &lt;td&gt;멀티턴 넛지 없음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;stop_hook_blocking&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;미구현&lt;/td&gt;
 &lt;td&gt;Stop 훅 없음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;token_budget_continuation&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;미구현&lt;/td&gt;
 &lt;td&gt;토큰 예산 시스템 없음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;next_turn&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;구현됨&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;도구 결과 후 API 재호출&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="가장-치명적인-갭-동기적-api-소비"&gt;가장 치명적인 갭: 동기적 API 소비
&lt;/h3&gt;&lt;p&gt;Rust의 &lt;code&gt;ApiClient&lt;/code&gt; 트레이트 시그니처가 모든 것을 말해준다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;: &lt;span class="nc"&gt;ApiRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AssistantEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RuntimeError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;반환 타입이 &lt;code&gt;Vec&amp;lt;AssistantEvent&amp;gt;&lt;/code&gt;다. &lt;strong&gt;스트리밍이 아니다.&lt;/strong&gt; SSE 이벤트를 모두 수집한 후 벡터로 반환한다. 이로 인해 모델이 5개 ReadFile을 호출할 때, TS는 첫 번째 ReadFile이 스트리밍 중에 실행 완료될 수 있지만, Rust는 5개 모두 스트리밍이 끝난 후에야 순차 실행을 시작한다. &lt;strong&gt;레이턴시 차이가 도구 수에 비례하여 증가&lt;/strong&gt;한다.&lt;/p&gt;
&lt;h2 id="6-rust-프로토타입--갭-브릿지"&gt;6. Rust 프로토타입 &amp;ndash; 갭 브릿지
&lt;/h2&gt;&lt;p&gt;S04 프로토타입에서 P0 갭 3개를 브릿지하는 오케스트레이션 레이어를 구현했다:&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 subgraph TS["TS 스트리밍 파이프라인"]
 direction TB
 ts1["SSE 이벤트 스트림"]
 ts2["StreamingToolExecutor&amp;lt;br/&amp;gt;4-state machine"]
 ts3["getCompletedResults()&amp;lt;br/&amp;gt;yield 순서 보장"]
 ts1 --&gt; ts2 --&gt; ts3
 end

 subgraph Rust["Rust 프로토타입"]
 direction TB
 rs1["EventStream&amp;lt;br/&amp;gt;tokio async"]
 rs2["StreamingPipeline&amp;lt;br/&amp;gt;tokio::spawn + mpsc"]
 rs3["MessageEnd 후&amp;lt;br/&amp;gt;채널 수집 + 정렬"]
 rs1 --&gt; rs2 --&gt; rs3
 end

 subgraph Bridge["핵심 매핑"]
 direction TB
 b1["yield -&gt; tx.send()"]
 b2["yield* -&gt; 채널 전달"]
 b3["for await -&gt; while let recv()"]
 end

 TS ~~~ Bridge ~~~ Rust

 style TS fill:#e1f5fe
 style Rust fill:#fff3e0
 style Bridge fill:#f3e5f5&lt;/pre&gt;&lt;h3 id="프로토타입의-3가지-구현"&gt;프로토타입의 3가지 구현
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;1. 비동기 스트리밍&lt;/strong&gt;: &lt;code&gt;ApiClient&lt;/code&gt; 트레이트를 비동기 스트림으로 확장. &lt;code&gt;MessageStream::next_event()&lt;/code&gt;가 이미 비동기이므로 소비 측만 변경하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 도구 파이프라이닝&lt;/strong&gt;: &lt;code&gt;ToolUseEnd&lt;/code&gt; 이벤트 수신 시 누적된 입력으로 &lt;code&gt;ToolCall&lt;/code&gt;을 조립하고 &lt;code&gt;tokio::spawn&lt;/code&gt;으로 즉시 백그라운드 실행을 시작한다. &lt;code&gt;mpsc::unbounded_channel&lt;/code&gt;로 완료 순서로 수집하고 나중에 원래 순서로 정렬한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 3-tier 동시성&lt;/strong&gt;: &lt;code&gt;ToolCategory&lt;/code&gt; enum(ReadOnly/Write/BashLike)에 따라 파티셔닝. ReadOnly 배치는 &lt;code&gt;Semaphore(10)&lt;/code&gt; + &lt;code&gt;tokio::spawn&lt;/code&gt;으로 최대 10개 병렬. BashLike는 순차 실행 + 에러 시 나머지 중단.&lt;/p&gt;
&lt;h3 id="프로토타입-커버리지"&gt;프로토타입 커버리지
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;TS 기능&lt;/th&gt;
 &lt;th&gt;프로토타입&lt;/th&gt;
 &lt;th&gt;상태&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;partitionToolCalls()&lt;/code&gt; 3-tier&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;partition_into_runs()&lt;/code&gt; + &lt;code&gt;ToolCategory&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;구현&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;runToolsConcurrently()&lt;/code&gt; max 10&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;Semaphore(10)&lt;/code&gt; + &lt;code&gt;tokio::spawn&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;구현&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;siblingAbortController&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;BashLike에서 &lt;code&gt;break&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;단순화&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;StreamingToolExecutor.addTool()&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;ToolUseEnd&lt;/code&gt; 시 &lt;code&gt;tokio::spawn&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;구현&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;PreToolUse hook deny/allow&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;HookDecision::Allow/Deny&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;구현&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;PostToolUse output transform&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;HookResult::transformed_output&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;구현&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;4-state machine (queued-&amp;gt;yielded)&lt;/td&gt;
 &lt;td&gt;spawned/completed 2-state&lt;/td&gt;
 &lt;td&gt;미완성&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;413 복구 / max_output 에스컬레이션&lt;/td&gt;
 &lt;td&gt;&amp;ndash;&lt;/td&gt;
 &lt;td&gt;미구현&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;preventContinuation&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&amp;ndash;&lt;/td&gt;
 &lt;td&gt;미구현&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="정지-조건-비교"&gt;정지 조건 비교
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;조건&lt;/th&gt;
 &lt;th&gt;TS&lt;/th&gt;
 &lt;th&gt;Rust&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;도구 없음 (end_turn)&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;handleStopHooks()&lt;/code&gt; 실행 후 종료&lt;/td&gt;
 &lt;td&gt;즉시 &lt;code&gt;break&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;토큰 예산 초과&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;checkTokenBudget()&lt;/code&gt; 3가지 결정&lt;/td&gt;
 &lt;td&gt;없음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;max_output_tokens&lt;/td&gt;
 &lt;td&gt;에스컬레이션 + 멀티턴 복구&lt;/td&gt;
 &lt;td&gt;없음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;413 prompt-too-long&lt;/td&gt;
 &lt;td&gt;컨텍스트 축소 + 반응형 압축&lt;/td&gt;
 &lt;td&gt;에러 전파&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;maxTurns&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;maxTurns&lt;/code&gt; 파라미터 (query.ts:1696)&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;max_iterations&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;수확 체감&lt;/td&gt;
 &lt;td&gt;3회 이상 + 500토큰 미만 증가&lt;/td&gt;
 &lt;td&gt;없음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;tokenBudget.ts&lt;/code&gt;(93줄)의 &lt;code&gt;checkTokenBudget()&lt;/code&gt;은 &lt;strong&gt;프롬프트 크기가 아닌 응답 연속 여부&lt;/strong&gt;를 제어한다. &lt;code&gt;COMPLETION_THRESHOLD = 0.9&lt;/code&gt;(전체 버짓의 90% 미만이면 계속), &lt;code&gt;DIMINISHING_THRESHOLD = 500&lt;/code&gt;(연속 3회 이상 매번 500토큰 미만 생성 시 수확 체감으로 중단). &lt;code&gt;nudgeMessage&lt;/code&gt;가 명시적으로 &amp;ldquo;do not summarize&amp;quot;를 지시한다.&lt;/p&gt;
&lt;h2 id="설계-결정의-핵심--왜-asyncgenerator인가"&gt;설계 결정의 핵심 &amp;ndash; 왜 AsyncGenerator인가
&lt;/h2&gt;&lt;p&gt;전체 파이프라인이 &lt;code&gt;async function*&lt;/code&gt; 체인이다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;QueryEngine.submitMessage()* -&amp;gt; query()* -&amp;gt; queryLoop()* -&amp;gt; deps.callModel()*
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;runTools()* -&amp;gt; runToolUse()* -&amp;gt; handleStopHooks()* -&amp;gt; executeStopHooks()*
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;이 선택의 핵심 이점: &lt;strong&gt;제어 흐름의 반전 없이 복잡한 상태 기계를 구현&lt;/strong&gt;할 수 있다. 7가지 &lt;code&gt;continue&lt;/code&gt; 경로에서 &lt;code&gt;state = { ... }&lt;/code&gt;로 상태를 명시적으로 구성하고 &lt;code&gt;continue&lt;/code&gt;하면 된다. 콜백 기반이었다면 상태 관리가 분산되어 7가지 복구 경로의 정합성을 보장하기 어려웠을 것이다.&lt;/p&gt;
&lt;p&gt;Rust에서는 &lt;code&gt;yield&lt;/code&gt; 키워드가 안정화되지 않았으므로, &lt;code&gt;tokio::sync::mpsc&lt;/code&gt; 채널로 대체한다. &lt;code&gt;yield&lt;/code&gt; -&amp;gt; &lt;code&gt;tx.send()&lt;/code&gt;, &lt;code&gt;yield*&lt;/code&gt; -&amp;gt; 채널 전달, &lt;code&gt;for await...of&lt;/code&gt; -&amp;gt; &lt;code&gt;while let Some(v) = rx.recv()&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="인사이트"&gt;인사이트
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;query.ts의 7가지 continue는 &amp;ldquo;에러 처리&amp;quot;가 아니라 &amp;ldquo;탄력성 엔진&amp;quot;이다&lt;/strong&gt; &amp;ndash; 413 에러에서 컨텍스트를 축소하고, max_output에서 토큰을 에스컬레이션하며, stop 훅 블로킹에서 에러를 모델에 피드백한다. 이 복구 파이프라인이 장시간 자율 작업의 안정성을 보장한다. Rust에서 이를 재현하려면 단순한 &lt;code&gt;loop {}&lt;/code&gt; 이상의 상태 관리가 필요하다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;StreamingToolExecutor는 성능 최적화가 아니라 UX 결정이다&lt;/strong&gt; &amp;ndash; 도구 5개를 직렬로 실행하면 사용자가 체감하는 대기 시간이 직렬 합산이 된다. 파이프라이닝은 벤치마크 수치가 아니라 &amp;ldquo;응답을 기다리는&amp;rdquo; 시간을 줄이는 사용자 경험 요소다. Rust 프로토타입에서 &lt;code&gt;tokio::spawn&lt;/code&gt; + &lt;code&gt;mpsc&lt;/code&gt; 채널로 이를 20줄 이내에 구현할 수 있었다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;정적 파티셔닝 + 런타임 동시성의 이중 구조가 안전성과 성능을 양립시킨다&lt;/strong&gt; &amp;ndash; &lt;code&gt;partitionToolCalls()&lt;/code&gt;가 컴파일 타임에 배치를 나누고, &lt;code&gt;canExecuteTool()&lt;/code&gt;이 런타임에 실행 가능 여부를 판단한다. 이 이중 구조 덕분에 비스트리밍 경로(&lt;code&gt;runTools&lt;/code&gt;)와 스트리밍 경로(&lt;code&gt;StreamingToolExecutor&lt;/code&gt;)가 동일한 동시성 의미론을 공유한다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;다음 포스트: &lt;a class="link" href="https://ice-ice-bear.github.io/posts/2026-04-06-harness-anatomy-3/" &gt;#3 &amp;ndash; 42개 도구의 설계 철학, BashTool부터 AgentTool까지&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Claude Code 하네스 해부학 #3 — 42개 도구의 설계 철학, BashTool부터 AgentTool까지</title><link>https://ice-ice-bear.github.io/ko/posts/2026-04-06-harness-anatomy-3/</link><pubDate>Mon, 06 Apr 2026 00:00:00 +0900</pubDate><guid>https://ice-ice-bear.github.io/ko/posts/2026-04-06-harness-anatomy-3/</guid><description>&lt;img src="https://ice-ice-bear.github.io/" alt="Featured image of post Claude Code 하네스 해부학 #3 — 42개 도구의 설계 철학, BashTool부터 AgentTool까지" /&gt;&lt;h2 id="개요"&gt;개요
&lt;/h2&gt;&lt;p&gt;Claude Code에는 42개의 도구가 존재한다. 이 포스트에서는 &lt;code&gt;Tool.ts&lt;/code&gt;의 30+ 멤버 인터페이스가 구현하는 &amp;ldquo;도구가 자신을 알고 있다&amp;rdquo; 패턴을 해부하고, 42개 도구를 8개 패밀리로 분류한다. 그 중 가장 복잡한 BashTool(12,411줄)의 6계층 보안 체인, AgentTool(6,782줄)의 4가지 스폰 모드, FileEditTool의 문자열 매칭 전략, MCPTool의 빈 껍데기 프록시 패턴, Task 상태 머신을 심층 분석한다.&lt;/p&gt;
&lt;h2 id="1-tool-인터페이스--도구가-자신을-알고-있다"&gt;1. Tool 인터페이스 &amp;ndash; &amp;ldquo;도구가 자신을 알고 있다&amp;rdquo;
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;Tool.ts&lt;/code&gt;(792줄)는 도구 시스템의 계약서다. 모든 도구가 구현하는 &lt;code&gt;Tool&lt;/code&gt; 타입(Tool.ts:362-695)은 &lt;strong&gt;30개 이상의 멤버&lt;/strong&gt;로 구성되며 네 영역으로 나뉜다:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;영역&lt;/th&gt;
 &lt;th&gt;핵심 멤버&lt;/th&gt;
 &lt;th&gt;역할&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;실행 계약&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;call()&lt;/code&gt;, &lt;code&gt;inputSchema&lt;/code&gt;, &lt;code&gt;validateInput()&lt;/code&gt;, &lt;code&gt;checkPermissions()&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;도구의 핵심 로직&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;메타데이터&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;aliases&lt;/code&gt;, &lt;code&gt;searchHint&lt;/code&gt;, &lt;code&gt;shouldDefer&lt;/code&gt;, &lt;code&gt;maxResultSizeChars&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;검색과 표시&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;동시성/안전성&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;isConcurrencySafe()&lt;/code&gt;, &lt;code&gt;isReadOnly()&lt;/code&gt;, &lt;code&gt;isDestructive()&lt;/code&gt;, &lt;code&gt;interruptBehavior()&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;오케스트레이션 결정&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;UI 렌더링&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;renderToolUseMessage()&lt;/code&gt; 등 10개+&lt;/td&gt;
 &lt;td&gt;터미널 표시&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;왜 이렇게 많은 멤버가 한 인터페이스에?&lt;/strong&gt; 오케스트레이터(&lt;code&gt;toolExecution.ts&lt;/code&gt;)가 도구를 호출할 때 외부 매핑 테이블 없이 도구 객체 자체에서 모든 메타데이터를 읽을 수 있다. 새 도구 추가가 &lt;strong&gt;한 디렉토리 안에서 완결&lt;/strong&gt;되는 플러그인 아키텍처의 근간이다.&lt;/p&gt;
&lt;h3 id="toolusecontext--42개-필드의-실행-환경"&gt;ToolUseContext &amp;ndash; 42개 필드의 실행 환경
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;ToolUseContext&lt;/code&gt;(Tool.ts:158-300)는 도구 실행 시 주입되는 환경 컨텍스트다. 142줄에 걸쳐 42개 필드가 정의되어 있다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;abortController&lt;/code&gt;: 3-tier 동시성 모델의 취소 전파&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getAppState()&lt;/code&gt;/&lt;code&gt;setAppState()&lt;/code&gt;: 전역 상태 접근 (권한, todo, 팀)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readFileState&lt;/code&gt;: LRU 캐시 기반 변경 감지&lt;/li&gt;
&lt;li&gt;&lt;code&gt;contentReplacementState&lt;/code&gt;: 대용량 결과를 디스크에 저장하고 요약만 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;도구는 격리된 함수가 아니라 하네스의 전체 상태에 접근해야 한다. FileReadTool은 캐시로 변경 여부를 판단하고, AgentTool은 서브에이전트 상태를 등록하며, BashTool은 형제 프로세스를 중단한다.&lt;/p&gt;
&lt;h3 id="buildtool의-fail-closed-기본값"&gt;buildTool()의 fail-closed 기본값
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;buildTool()&lt;/code&gt;(Tool.ts:783)은 &lt;code&gt;ToolDef&lt;/code&gt;를 받아 기본값을 채운 완전한 &lt;code&gt;Tool&lt;/code&gt;을 반환한다. 기본값은 &lt;strong&gt;fail-closed&lt;/strong&gt; 원칙(Tool.ts:757-768):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;isConcurrencySafe&lt;/code&gt; -&amp;gt; &lt;code&gt;false&lt;/code&gt; (안전하지 않다고 가정)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isReadOnly&lt;/code&gt; -&amp;gt; &lt;code&gt;false&lt;/code&gt; (쓰기한다고 가정)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;새 도구를 만들 때 동시성/읽기전용을 명시적으로 선언하지 않으면 가장 보수적인 경로(순차 실행, 쓰기 권한 요구)를 탄다. &lt;strong&gt;실수로 안전하지 않은 도구를 병렬 실행하는 버그를 구조적으로 방지&lt;/strong&gt;한다.&lt;/p&gt;
&lt;h2 id="2-42개-도구의-8개-패밀리"&gt;2. 42개 도구의 8개 패밀리
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 subgraph safe["isConcurrencySafe: true (10개)"]
 direction TB
 R1["FileReadTool"]
 R2["GlobTool / GrepTool"]
 R3["WebFetchTool / WebSearchTool"]
 R4["ToolSearchTool / SleepTool"]
 R5["TaskGetTool / TaskListTool"]
 R6["LSPTool"]
 end

 subgraph unsafe["isConcurrencySafe: false (32개)"]
 direction TB
 W1["BashTool 12,411줄"]
 W2["FileEditTool / FileWriteTool"]
 W3["AgentTool 6,782줄"]
 W4["MCPTool / SkillTool"]
 W5["Task 5개 / Todo"]
 W6["Config / PlanMode / Worktree"]
 end

 subgraph orch["오케스트레이터"]
 O["partitionToolCalls()&amp;lt;br/&amp;gt;toolOrchestration.ts"]
 end

 O --&gt;|"병렬 배치"| safe
 O --&gt;|"순차 실행"| unsafe

 style safe fill:#e8f5e9
 style unsafe fill:#ffebee&lt;/pre&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;패밀리&lt;/th&gt;
 &lt;th&gt;도구 수&lt;/th&gt;
 &lt;th&gt;대표 도구&lt;/th&gt;
 &lt;th&gt;핵심 특징&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;파일시스템&lt;/td&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;td&gt;FileReadTool (1,602줄)&lt;/td&gt;
 &lt;td&gt;PDF/이미지/노트북 지원, 토큰 제한&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;실행&lt;/td&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;BashTool (12,411줄)&lt;/td&gt;
 &lt;td&gt;6계층 보안, 명령 의미론&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;에이전트/팀&lt;/td&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;AgentTool (6,782줄)&lt;/td&gt;
 &lt;td&gt;4가지 스폰, 재귀적 하네스&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;태스크 관리&lt;/td&gt;
 &lt;td&gt;7&lt;/td&gt;
 &lt;td&gt;TaskUpdateTool (484줄)&lt;/td&gt;
 &lt;td&gt;상태 머신, 검증 넛지&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;MCP/LSP&lt;/td&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;td&gt;MCPTool (1,086줄)&lt;/td&gt;
 &lt;td&gt;빈 껍데기 프록시&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;웹/외부&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;WebFetchTool (1,131줄)&lt;/td&gt;
 &lt;td&gt;병렬 안전&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;상태/설정&lt;/td&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;td&gt;ConfigTool (809줄)&lt;/td&gt;
 &lt;td&gt;세션 상태 변경&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;인프라/유틸&lt;/td&gt;
 &lt;td&gt;11&lt;/td&gt;
 &lt;td&gt;SkillTool (1,477줄)&lt;/td&gt;
 &lt;td&gt;커맨드-도구 브리지&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;42개 중 10개(24%)만 병렬 실행 가능하지만, 이 10개가 가장 빈번하게 호출되는 도구(Read, Glob, Grep, Web)이므로 실제 체감 병렬성은 비율보다 높다.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="3-bashtool--6계층-보안-체인"&gt;3. BashTool &amp;ndash; 6계층 보안 체인
&lt;/h2&gt;&lt;p&gt;BashTool은 단순한 셸 실행기가 아니다. &lt;strong&gt;임의 코드 실행&lt;/strong&gt;이라는 본질적 위험 때문에 12,411줄의 절반 이상이 보안 레이어다.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB
 A["모델: Bash 호출"] --&gt; B{"validateInput"}
 B --&gt;|"sleep 패턴 차단"| B1["에러 반환"]
 B --&gt;|"통과"| C{"6계층 보안 체인"}

 subgraph chain["보안 체인"]
 C1["1. bashSecurity.ts&amp;lt;br/&amp;gt;2,592줄 -- 명령 구조 분석"]
 C2["2. bashPermissions.ts&amp;lt;br/&amp;gt;2,621줄 -- 규칙 매칭"]
 C3["3. readOnlyValidation.ts&amp;lt;br/&amp;gt;1,990줄 -- 읽기전용 판단"]
 C4["4. pathValidation.ts&amp;lt;br/&amp;gt;1,303줄 -- 경로 기반 보안"]
 C5["5. sedValidation.ts&amp;lt;br/&amp;gt;684줄 -- sed 전용 보안"]
 C6["6. shouldUseSandbox.ts&amp;lt;br/&amp;gt;153줄 -- 샌드박스 결정"]
 C1 --&gt; C2 --&gt; C3 --&gt; C4 --&gt; C5 --&gt; C6
 end

 C --&gt; chain
 chain --&gt; D{"allow / ask / deny"}
 D --&gt;|"allow"| E["runShellCommand()"]
 D --&gt;|"ask"| F["사용자 승인 요청"]
 D --&gt;|"deny"| G["거부"]
 E --&gt; H["결과 처리&amp;lt;br/&amp;gt;interpretCommandResult()&amp;lt;br/&amp;gt;trackGitOperations()"]

 style chain fill:#fff3e0&lt;/pre&gt;&lt;p&gt;각 레이어가 다른 위협을 담당한다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;bashSecurity.ts&lt;/strong&gt; (2,592줄): 명령 치환(&lt;code&gt;$()&lt;/code&gt;, &lt;code&gt;`&lt;/code&gt;), Zsh 모듈 기반 공격 차단. 핵심: &lt;strong&gt;unquoted 컨텍스트의 메타문자만 위험으로 분류&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bashPermissions.ts&lt;/strong&gt; (2,621줄): 규칙 기반 allow/deny/ask. &lt;code&gt;stripAllLeadingEnvVars()&lt;/code&gt; + &lt;code&gt;stripSafeWrappers()&lt;/code&gt;로 래퍼 제거 후 실제 명령 추출&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;readOnlyValidation.ts&lt;/strong&gt; (1,990줄): 읽기전용이면 &lt;code&gt;isConcurrencySafe: true&lt;/code&gt; &amp;ndash; 병렬 실행 허용&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pathValidation.ts&lt;/strong&gt; (1,303줄): 명령별 경로 추출 규칙으로 경로 안전성 판단&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sedValidation.ts&lt;/strong&gt; (684줄): sed의 &lt;code&gt;w&lt;/code&gt;, &lt;code&gt;e&lt;/code&gt; 플래그는 파일 쓰기/임의 실행 가능 &amp;ndash; 별도 차단&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shouldUseSandbox.ts&lt;/strong&gt; (153줄): 최종 격리 결정&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;명령 의미론&lt;/strong&gt; (&lt;code&gt;commandSemantics.ts&lt;/code&gt;): &lt;code&gt;grep&lt;/code&gt;과 &lt;code&gt;diff&lt;/code&gt;는 exit code 1이 에러가 아닌 정상 결과다. &lt;code&gt;COMMAND_SEMANTICS&lt;/code&gt; Map으로 명령별 해석 규칙을 정의한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rust 포팅 시사점&lt;/strong&gt;: 이 6계층을 통째로 재현하거나, 아예 sandbox-only로 단순화해야 한다. 중간 단계 생략은 보안 구멍을 만든다.&lt;/p&gt;
&lt;h2 id="4-agenttool--4가지-스폰-모드"&gt;4. AgentTool &amp;ndash; 4가지 스폰 모드
&lt;/h2&gt;&lt;p&gt;AgentTool은 &amp;ldquo;도구&amp;quot;라기보다 &lt;strong&gt;에이전트 오케스트레이터&lt;/strong&gt;다. 핵심: &lt;code&gt;runAgent()&lt;/code&gt;는 하네스의 &lt;code&gt;query()&lt;/code&gt; 루프를 재귀 호출한다. 자식 에이전트는 부모와 동일한 도구/API/보안 체크를 받는다.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;모드&lt;/th&gt;
 &lt;th&gt;트리거&lt;/th&gt;
 &lt;th&gt;컨텍스트 공유&lt;/th&gt;
 &lt;th&gt;백그라운드&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;동기&lt;/td&gt;
 &lt;td&gt;기본&lt;/td&gt;
 &lt;td&gt;없음 (프롬프트만)&lt;/td&gt;
 &lt;td&gt;X&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;비동기&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;run_in_background: true&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;없음&lt;/td&gt;
 &lt;td&gt;O&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;포크&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;subagent_type&lt;/code&gt; 생략&lt;/td&gt;
 &lt;td&gt;부모 전체 컨텍스트&lt;/td&gt;
 &lt;td&gt;O&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;원격&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;isolation: &amp;quot;remote&amp;quot;&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;없음&lt;/td&gt;
 &lt;td&gt;O&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="포크-서브에이전트--byte-identical-프리픽스"&gt;포크 서브에이전트 &amp;ndash; byte-identical 프리픽스
&lt;/h3&gt;&lt;p&gt;포크는 부모의 &lt;strong&gt;전체 대화 컨텍스트를 상속&lt;/strong&gt;한다. 프롬프트 캐시 공유를 위해 모든 포크 자식이 byte-identical API 요청 프리픽스를 생성하도록 설계한다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;도구 사용 결과를 플레이스홀더로 대체&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FORK_BOILERPLATE_TAG&lt;/code&gt;로 재귀 포크 방지&lt;/li&gt;
&lt;li&gt;모델 동일 유지 (&lt;code&gt;model: 'inherit'&lt;/code&gt;) &amp;ndash; 다른 모델은 캐시 불일치&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="메모리-시스템-agentmemoryts"&gt;메모리 시스템 (agentMemory.ts)
&lt;/h3&gt;&lt;p&gt;에이전트별 지속 메모리를 3가지 스코프로 관리:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;user&lt;/strong&gt;: &lt;code&gt;~/.claude/agent-memory/&amp;lt;type&amp;gt;/&lt;/code&gt; &amp;ndash; 사용자 전역&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;project&lt;/strong&gt;: &lt;code&gt;.claude/agent-memory/&amp;lt;type&amp;gt;/&lt;/code&gt; &amp;ndash; 프로젝트 공유 (VCS)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;local&lt;/strong&gt;: &lt;code&gt;.claude/agent-memory-local/&amp;lt;type&amp;gt;/&lt;/code&gt; &amp;ndash; 로컬 전용&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="5-fileedittool--부분-대체-패턴"&gt;5. FileEditTool &amp;ndash; 부분 대체 패턴
&lt;/h2&gt;&lt;p&gt;FileEditTool(1,812줄)은 전체 파일 쓰기가 아닌 &lt;strong&gt;&lt;code&gt;old_string&lt;/code&gt; -&amp;gt; &lt;code&gt;new_string&lt;/code&gt; 패치&lt;/strong&gt;를 수행한다. 모델이 전체 파일을 출력할 필요가 없어 토큰을 절약하고, diff 기반 리뷰를 가능하게 한다.&lt;/p&gt;
&lt;p&gt;매칭 전략:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;정확한 문자열 매칭&lt;/strong&gt;: &lt;code&gt;fileContent.includes(searchString)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;따옴표 정규화&lt;/strong&gt;: curly quote -&amp;gt; straight quote 변환 후 재시도, &lt;code&gt;preserveQuoteStyle()&lt;/code&gt;로 원본 스타일 보존&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;고유성 검증&lt;/strong&gt;: &lt;code&gt;old_string&lt;/code&gt;이 파일에서 유일하지 않으면 실패 (또는 &lt;code&gt;replace_all&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;동시성 보호&lt;/strong&gt;: &lt;code&gt;readFileState&lt;/code&gt; Map에 파일별 마지막 읽기 타임스탬프를 저장. 편집 시 디스크 수정 시간과 비교하여 외부 변경을 감지한다. 이것이 &amp;ldquo;Read 후 Edit&amp;rdquo; 규칙이 프롬프트에서 강제되는 이유다.&lt;/p&gt;
&lt;h2 id="6-mcptool--빈-껍데기-프록시"&gt;6. MCPTool &amp;ndash; 빈 껍데기 프록시
&lt;/h2&gt;&lt;p&gt;MCPTool(1,086줄)은 &lt;strong&gt;하나의 도구 정의가 수백 개의 외부 도구를 대표&lt;/strong&gt;한다. 빌드 시점에는 빈 껍데기이고, 런타임에 &lt;code&gt;mcpClient.ts&lt;/code&gt;가 서버별로 복제 + 오버라이드한다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// MCPTool.ts:27-51 -- 핵심 메서드가 &amp;#34;Overridden in mcpClient.ts&amp;#34; 주석
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;mcp&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 런타임에 &amp;#39;mcp__serverName__toolName&amp;#39;으로 교체
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;call() {&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// 런타임에 실제 MCP 호출로 교체
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;UI 축소 분류(&lt;code&gt;classifyForCollapse.ts&lt;/code&gt;, 604줄)는 139개 SEARCH_TOOLS, 280개+ READ_TOOLS 이름으로 MCP 도구의 읽기/검색 여부를 판단한다. 알 수 없는 도구는 축소하지 않는다(보수적).&lt;/p&gt;
&lt;h2 id="7-task-상태-머신--에이전트-ipc"&gt;7. Task 상태 머신 &amp;ndash; 에이전트 IPC
&lt;/h2&gt;&lt;p&gt;TaskUpdateTool(406줄)의 상태 흐름: &lt;code&gt;pending -&amp;gt; in_progress -&amp;gt; completed&lt;/code&gt; 또는 &lt;code&gt;deleted&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;핵심 행동:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;소유자 자동 설정&lt;/strong&gt;: &lt;code&gt;in_progress&lt;/code&gt; 변경 시 현재 에이전트 이름 자동 할당&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;검증 넛지&lt;/strong&gt;: 3개+ 태스크 완료 후 검증 단계가 없으면 verification agent 스폰 권고&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;메시지 라우팅&lt;/strong&gt; (SendMessageTool 917줄): 이름, &lt;code&gt;*&lt;/code&gt; 브로드캐스트, &lt;code&gt;uds:path&lt;/code&gt; Unix 도메인 소켓, &lt;code&gt;bridge:session&lt;/code&gt; 원격 피어, 에이전트 ID 재개&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Task/SendMessage는 단순한 유틸리티가 아니라 멀티에이전트 시스템의 &lt;strong&gt;프로세스 간 통신(IPC)&lt;/strong&gt; 기초다.&lt;/p&gt;
&lt;h2 id="ts-vs-rust-비교"&gt;TS vs Rust 비교
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;측면&lt;/th&gt;
 &lt;th&gt;TS (42개 도구)&lt;/th&gt;
 &lt;th&gt;Rust (10개 도구)&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;도구 정의&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;Tool&lt;/code&gt; 인터페이스 + &lt;code&gt;buildTool()&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;ToolSpec&lt;/code&gt; 구조체 + &lt;code&gt;mvp_tool_specs()&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;입력 스키마&lt;/td&gt;
 &lt;td&gt;Zod v4 + &lt;code&gt;lazySchema()&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;serde_json::json!()&lt;/code&gt; JSON Schema 직접&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;동시성 선언&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;isConcurrencySafe(parsedInput)&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;없음 &amp;ndash; 순차 실행&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;권한 검사&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;checkPermissions()&lt;/code&gt; -&amp;gt; &lt;code&gt;PermissionResult&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;PermissionMode&lt;/code&gt; enum&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;UI 렌더링&lt;/td&gt;
 &lt;td&gt;10+ render 메서드 (React/Ink)&lt;/td&gt;
 &lt;td&gt;없음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;MCP 통합&lt;/td&gt;
 &lt;td&gt;MCPTool + &lt;code&gt;inputJSONSchema&lt;/code&gt; 이중 경로&lt;/td&gt;
 &lt;td&gt;없음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;크기 비교&lt;/td&gt;
 &lt;td&gt;~48,000줄 (도구 코드만)&lt;/td&gt;
 &lt;td&gt;~1,300줄 (lib.rs 단일)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;핵심 격차&lt;/strong&gt;: Rust 포트는 실행 계약(&lt;code&gt;call&lt;/code&gt; 등가물)만 구현하고, 동시성 선언, 권한 파이프라인, UI 렌더링, 지연 로딩 최적화는 모두 없다.&lt;/p&gt;
&lt;h2 id="인사이트"&gt;인사이트
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;보안은 단일 체크포인트가 아니라 체인이다&lt;/strong&gt; &amp;ndash; BashTool의 6계층은 각 레이어가 다른 위협을 담당한다. bashSecurity는 명령 구조, bashPermissions는 규칙 매칭, pathValidation은 경로 안전성. 이 체인의 어느 한 곳이라도 빠지면 공격 표면이 열린다. fail-closed 원칙과 결합하여 &amp;ldquo;모르면 차단&amp;quot;이라는 보수적 전략이 전체 시스템을 관통한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;에이전트는 재귀적 하네스 인스턴스다&lt;/strong&gt; &amp;ndash; AgentTool의 &lt;code&gt;runAgent()&lt;/code&gt;가 하네스의 &lt;code&gt;query()&lt;/code&gt; 루프를 재귀 호출한다는 것은, &amp;ldquo;에이전트&amp;quot;가 별개의 시스템이 아니라 **같은 하네스의 다른 구성(configuration)**이라는 아키텍처 결정이다. 도구 풀만 교체하고 동일한 보안/훅/오케스트레이션을 재사용한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;42개 도구 중 10개만 concurrency-safe이지만 체감 병렬성은 높다&lt;/strong&gt; &amp;ndash; 전체의 24%에 불과한 10개 도구(Read, Glob, Grep, Web, LSP)가 가장 빈번하게 호출된다. 이 비대칭이 3-tier 동시성 모델의 실용적 가치를 보여준다. &lt;code&gt;buildTool()&lt;/code&gt;의 fail-closed 기본값(&lt;code&gt;isConcurrencySafe: false&lt;/code&gt;)이 안전 경계를 형성하여, 새 도구 개발자가 동시성을 잘못 선언하는 실수를 구조적으로 방지한다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;다음 포스트: &lt;a class="link" href="https://ice-ice-bear.github.io/posts/2026-04-06-harness-anatomy-4/" &gt;#4 &amp;ndash; 런타임 훅 26+이벤트와 CLAUDE.md 6단계 디스커버리&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Claude Code 하네스 해부학 #4 — 런타임 훅 26+이벤트와 CLAUDE.md 6단계 디스커버리</title><link>https://ice-ice-bear.github.io/ko/posts/2026-04-06-harness-anatomy-4/</link><pubDate>Mon, 06 Apr 2026 00:00:00 +0900</pubDate><guid>https://ice-ice-bear.github.io/ko/posts/2026-04-06-harness-anatomy-4/</guid><description>&lt;img src="https://ice-ice-bear.github.io/" alt="Featured image of post Claude Code 하네스 해부학 #4 — 런타임 훅 26+이벤트와 CLAUDE.md 6단계 디스커버리" /&gt;&lt;h2 id="개요"&gt;개요
&lt;/h2&gt;&lt;p&gt;Claude Code에서 &amp;ldquo;hook&amp;quot;이라는 단어는 &lt;strong&gt;두 가지 완전히 다른 시스템&lt;/strong&gt;을 가리킨다. 런타임 훅(&lt;code&gt;toolHooks.ts&lt;/code&gt; + &lt;code&gt;utils/hooks.ts&lt;/code&gt;)은 도구 실행 전후에 셸 스크립트를 실행하는 보안/확장 파이프라인이고, React 훅(&lt;code&gt;hooks/*.ts&lt;/code&gt; 85개+)은 터미널 UI의 상태 관리 코드다. 이 구분을 놓치면 Rust 재구현 범위를 85배 오판하게 된다. 이번 포스트에서는 런타임 훅의 PreToolUse/PostToolUse 파이프라인과 &lt;code&gt;resolveHookPermissionDecision()&lt;/code&gt;의 보안 불변식, 85개 React 훅의 9개 카테고리 분류, 그리고 CLAUDE.md 6단계 디스커버리와 토큰 버짓 관리를 분석한다.&lt;/p&gt;
&lt;h2 id="1-런타임-훅-vs-react-훅--핵심-구분"&gt;1. 런타임 훅 vs React 훅 &amp;ndash; 핵심 구분
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;차원&lt;/th&gt;
 &lt;th&gt;런타임 훅 (toolHooks.ts + utils/hooks.ts)&lt;/th&gt;
 &lt;th&gt;React 훅 (hooks/*.ts)&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;실행 주체&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;child_process.spawn()&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;React 렌더 사이클&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;구성 방법&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;settings.json &lt;code&gt;hooks&lt;/code&gt; 필드, 셸 커맨드&lt;/td&gt;
 &lt;td&gt;소스코드 &lt;code&gt;import&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;실행 시점&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;도구 사용 전/후, 세션 시작 등 26+ 이벤트&lt;/td&gt;
 &lt;td&gt;컴포넌트 마운트/업데이트&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;사용자 정의&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;가능 &amp;ndash; 사용자가 셸 스크립트 등록&lt;/td&gt;
 &lt;td&gt;불가능 &amp;ndash; 내부 코드&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;결과 형태&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;JSON stdout (allow/deny/ask/rewrite)&lt;/td&gt;
 &lt;td&gt;React state 변경&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Rust 재구현&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;필수 &amp;ndash; 도구 실행 파이프라인의 핵심&lt;/td&gt;
 &lt;td&gt;불필요 &amp;ndash; TUI 전용&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="2-pretooluse-파이프라인--7가지-yield-변형"&gt;2. PreToolUse 파이프라인 &amp;ndash; 7가지 yield 변형
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;runPreToolUseHooks()&lt;/code&gt;(toolHooks.ts:435-650)는 AsyncGenerator로 설계되어 있다. 도구 실행 전에 호출되며 다음 yield 타입들을 방출한다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;message&lt;/code&gt;&lt;/strong&gt;: 진행 상황 메시지 (훅 시작/에러/취소)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;hookPermissionResult&lt;/code&gt;&lt;/strong&gt;: allow/deny/ask 결정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;hookUpdatedInput&lt;/code&gt;&lt;/strong&gt;: 입력 재작성 (권한 결정 없이 입력만 변경)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;preventContinuation&lt;/code&gt;&lt;/strong&gt;: 실행 중단 플래그&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;stopReason&lt;/code&gt;&lt;/strong&gt;: 중단 사유 문자열&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;additionalContext&lt;/code&gt;&lt;/strong&gt;: 모델에 전달할 추가 컨텍스트&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;stop&lt;/code&gt;&lt;/strong&gt;: 즉시 중단&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;왜 AsyncGenerator인가?&lt;/strong&gt; 훅은 여러 개가 순차 실행되고, 각 훅의 결과가 다음 처리에 영향을 미친다. Promise 체이닝은 최종 결과만 반환하고, 이벤트 이미터는 타입 안전성이 없다. AsyncGenerator는 호출자가 각 결과를 소비하면서 중간에 중단할 수 있는 유일한 패턴이다.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 subgraph "PreToolUse 파이프라인"
 A["toolExecution.ts&amp;lt;br/&amp;gt;도구 호출 시작"]
 B["runPreToolUseHooks()&amp;lt;br/&amp;gt;toolHooks.ts:435"]
 C["getMatchingHooks()&amp;lt;br/&amp;gt;utils/hooks.ts:1603"]
 D["settings.json hooks&amp;lt;br/&amp;gt;이벤트+패턴 매칭"]
 E["spawn() 셸 커맨드&amp;lt;br/&amp;gt;stdin: JSON, stdout: 결과"]
 F["HookResult 파싱&amp;lt;br/&amp;gt;allow / deny / ask / rewrite"]
 end

 subgraph "권한 해석"
 G["resolveHookPermission&amp;lt;br/&amp;gt;Decision()&amp;lt;br/&amp;gt;toolHooks.ts:332"]
 H{"훅 결과?"}
 I["allow: checkRule&amp;lt;br/&amp;gt;BasedPermissions()&amp;lt;br/&amp;gt;규칙이 훅을 오버라이드"]
 J["deny: 즉시 거부"]
 K["ask: canUseTool()&amp;lt;br/&amp;gt;사용자 프롬프트"]
 end

 subgraph "도구 실행"
 L["tool.call()"]
 end

 subgraph "PostToolUse"
 M["runPostToolUseHooks()&amp;lt;br/&amp;gt;결과 변환 / 차단"]
 end

 A --&gt; B --&gt; C --&gt; D --&gt; E --&gt; F --&gt; G --&gt; H
 H --&gt;|"allow"| I
 H --&gt;|"deny"| J
 H --&gt;|"ask"| K
 I --&gt;|"규칙 통과"| L
 L --&gt; M&lt;/pre&gt;&lt;h3 id="resolvehookpermissiondecision--allow--bypass"&gt;resolveHookPermissionDecision &amp;ndash; allow != bypass
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;resolveHookPermissionDecision()&lt;/code&gt;(toolHooks.ts:332-433)의 핵심 불변식: &lt;strong&gt;훅의 &lt;code&gt;allow&lt;/code&gt;가 settings.json의 deny/ask 규칙을 바이패스하지 않는다&lt;/strong&gt; (toolHooks.ts:325-327).&lt;/p&gt;
&lt;p&gt;처리 로직 3단계:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1단계 &amp;ndash; allow 처리&lt;/strong&gt; (toolHooks.ts:347-406):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hookResult.behavior === &amp;#39;allow&amp;#39;:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; checkRuleBasedPermissions() 호출
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; null -&amp;gt; 규칙 없음, 훅 허용 통과
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; deny -&amp;gt; 규칙이 훅을 오버라이드 (보안 우선!)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; ask -&amp;gt; 사용자 프롬프트 필요
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;왜 allow가 bypass하지 않는가?&lt;/strong&gt; 이것은 의도적 보안 결정이다. 외부 셸 스크립트가 &lt;code&gt;{&amp;quot;decision&amp;quot;:&amp;quot;allow&amp;quot;}&lt;/code&gt;를 반환한다고 해서 &lt;code&gt;settings.json&lt;/code&gt;의 &lt;code&gt;deny&lt;/code&gt; 규칙을 무시하면, 악의적 훅이 보안 정책을 우회할 수 있다. &lt;strong&gt;규칙은 항상 훅보다 우선한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2단계 &amp;ndash; deny&lt;/strong&gt; (toolHooks.ts:408-411): 즉시 거부, 추가 검사 없음.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3단계 &amp;ndash; ask/없음&lt;/strong&gt; (toolHooks.ts:413-432): &lt;code&gt;canUseTool()&lt;/code&gt; 호출로 사용자 프롬프트.&lt;/p&gt;
&lt;h3 id="26개-이상의-이벤트-유형"&gt;26개 이상의 이벤트 유형
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;getMatchingHooks()&lt;/code&gt;(utils/hooks.ts:1603-1682)이 훅 매칭을 담당한다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;도구 이벤트&lt;/strong&gt;: PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;세션 이벤트&lt;/strong&gt;: SessionStart, SessionEnd, Setup&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;에이전트 이벤트&lt;/strong&gt;: SubagentStart, SubagentStop, TeammateIdle&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;작업 이벤트&lt;/strong&gt;: TaskCreated, TaskCompleted&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;시스템 이벤트&lt;/strong&gt;: Notification, ConfigChange, FileChanged, InstructionsLoaded&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;컴팩트 이벤트&lt;/strong&gt;: PreCompact, PostCompact&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;입력 이벤트&lt;/strong&gt;: UserPromptSubmit, Elicitation, ElicitationResult&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;중단 이벤트&lt;/strong&gt;: Stop, StopFailure&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;매칭된 훅들은 &lt;strong&gt;순차 실행&lt;/strong&gt;되며, 하나가 deny하면 이후 훅은 실행되지 않는다.&lt;/p&gt;
&lt;h2 id="3-85개-react-훅--9개-카테고리-분류"&gt;3. 85개 React 훅 &amp;ndash; 9개 카테고리 분류
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;mindmap
 root(("TS 훅 시스템"))
 런타임 훅
 toolHooks.ts 651줄
 PreToolUse
 PostToolUse
 PostToolUseFailure
 utils/hooks.ts ~5000줄
 26+ 이벤트 유형
 셸 스폰
 비동기 프로토콜
 React 훅 85+
 권한 3개
 useCanUseTool
 PermissionContext
 UI 입력 11개
 useTextInput
 useVimInput
 useTypeahead
 UI 디스플레이 11개
 useVirtualScroll
 useDiffData
 상태/설정 12개
 useSettings
 useSessionBackgrounding
 통합/원격 12개
 useRemoteSession
 useReplBridge
 기능 20개
 useVoice
 useSwarm
 useTasks
 알림 16개
 notifs/ 디렉토리
 도구/키바인딩 5개
 useMergedTools
 추가 5+개
 fileSuggestions
 useManagePlugins&lt;/pre&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;카테고리&lt;/th&gt;
 &lt;th&gt;개수&lt;/th&gt;
 &lt;th&gt;Rust 재구현&lt;/th&gt;
 &lt;th&gt;대표 훅&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;권한&lt;/td&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;부분 (브릿지)&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useCanUseTool&lt;/code&gt; (203줄)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;UI 입력&lt;/td&gt;
 &lt;td&gt;11&lt;/td&gt;
 &lt;td&gt;불필요&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useTextInput&lt;/code&gt; (529줄), &lt;code&gt;useVimInput&lt;/code&gt; (316줄)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;UI 디스플레이&lt;/td&gt;
 &lt;td&gt;11&lt;/td&gt;
 &lt;td&gt;불필요&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useVirtualScroll&lt;/code&gt; (721줄)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;상태/설정&lt;/td&gt;
 &lt;td&gt;12&lt;/td&gt;
 &lt;td&gt;불필요&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useSessionBackgrounding&lt;/code&gt; (158줄)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;통합/원격&lt;/td&gt;
 &lt;td&gt;12&lt;/td&gt;
 &lt;td&gt;불필요&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useRemoteSession&lt;/code&gt; (605줄)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;기능/알림&lt;/td&gt;
 &lt;td&gt;20&lt;/td&gt;
 &lt;td&gt;불필요&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useVoice&lt;/code&gt; (1,144줄)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;알림/배너&lt;/td&gt;
 &lt;td&gt;16&lt;/td&gt;
 &lt;td&gt;불필요&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;notifs/&lt;/code&gt; 디렉토리&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;도구/키바인딩&lt;/td&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;td&gt;불필요&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useMergedTools&lt;/code&gt; (44줄)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;추가&lt;/td&gt;
 &lt;td&gt;5+&lt;/td&gt;
 &lt;td&gt;불필요&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;fileSuggestions&lt;/code&gt; (811줄)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;핵심&lt;/strong&gt;: Rust가 재구현해야 하는 것은 &lt;code&gt;toolHooks.ts&lt;/code&gt;(651줄) + &lt;code&gt;utils/hooks.ts&lt;/code&gt;(~5,000줄)의 런타임 파이프라인뿐이다. 85개 React 훅 15,000줄+은 범위 밖이다.&lt;/p&gt;
&lt;h2 id="4-claudemd-6단계-디스커버리"&gt;4. CLAUDE.md 6단계 디스커버리
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;claudemd.ts&lt;/code&gt;(1,479줄)의 &lt;code&gt;getMemoryFiles()&lt;/code&gt;(L790-1074)는 6단계 계층으로 CLAUDE.md를 로드한다:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;단계&lt;/th&gt;
 &lt;th&gt;소스&lt;/th&gt;
 &lt;th&gt;경로 예시&lt;/th&gt;
 &lt;th&gt;우선순위&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;1. Managed&lt;/td&gt;
 &lt;td&gt;조직 정책&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;/etc/claude-code/CLAUDE.md&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;최저&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2. User&lt;/td&gt;
 &lt;td&gt;개인 습관&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt;, &lt;code&gt;~/.claude/rules/*.md&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3. Project&lt;/td&gt;
 &lt;td&gt;프로젝트 규칙&lt;/td&gt;
 &lt;td&gt;cwd에서 루트까지 &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;.claude/rules/*.md&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;4. Local&lt;/td&gt;
 &lt;td&gt;로컬 오버라이드&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;CLAUDE.local.md&lt;/code&gt; (gitignore)&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;5. AutoMem&lt;/td&gt;
 &lt;td&gt;자동 메모리&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;MEMORY.md&lt;/code&gt; 엔트리포인트&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;6. TeamMem&lt;/td&gt;
 &lt;td&gt;팀 메모리&lt;/td&gt;
 &lt;td&gt;조직 간 동기화&lt;/td&gt;
 &lt;td&gt;최고&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;왜 이 순서인가?&lt;/strong&gt; 파일 주석(L9)이 명시한다: &amp;ldquo;Files are loaded in reverse order of priority.&amp;rdquo; LLM은 프롬프트 후반부에 더 주의를 기울이므로, 가장 구체적인 지시(Local &amp;gt; Project &amp;gt; User &amp;gt; Managed)가 &lt;strong&gt;마지막에 위치&lt;/strong&gt;한다. 이것은 CSS specificity가 아니라 &lt;strong&gt;LLM 주의 편향&lt;/strong&gt;을 활용한 설계다.&lt;/p&gt;
&lt;h3 id="디렉토리-상향-탐색과-중복-방지"&gt;디렉토리 상향 탐색과 중복 방지
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;originalCwd&lt;/code&gt;에서 파일시스템 루트까지 올라간 뒤 &lt;code&gt;dirs.reverse()&lt;/code&gt;로 &lt;strong&gt;루트부터 아래로&lt;/strong&gt; 처리한다 (L851-857). 모노레포에서 상위 &lt;code&gt;CLAUDE.md&lt;/code&gt;가 먼저 로드되고 하위 프로젝트의 &lt;code&gt;CLAUDE.md&lt;/code&gt;가 그 위에 오는 효과를 만든다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;워크트리 중복 방지&lt;/strong&gt; (L868-884): git 워크트리가 메인 레포 내부에 중첩되면 동일 &lt;code&gt;CLAUDE.md&lt;/code&gt;가 두 번 로드되는 것을 &lt;code&gt;isNestedWorktree&lt;/code&gt; 검사로 방지한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;@include 지시자&lt;/strong&gt; (L451-535): 마크다운 토큰을 렉싱하여 코드 블록 내부의 &lt;code&gt;@path&lt;/code&gt;는 무시하고, 텍스트 노드의 &lt;code&gt;@path&lt;/code&gt;만 재귀적으로 해석한다. 최대 깊이 5.&lt;/p&gt;
&lt;h2 id="5-시스템사용자-컨텍스트-분리--dual-memoize-캐시"&gt;5. 시스템/사용자 컨텍스트 분리 &amp;ndash; dual-memoize 캐시
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;context.ts&lt;/code&gt;(189줄)는 시스템 프롬프트를 &lt;strong&gt;두 개의 독립적인 컨텍스트&lt;/strong&gt;로 분리한다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;getSystemContext()&lt;/code&gt;&lt;/strong&gt; (L116): git 상태, 캐시 브레이커&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;getUserContext()&lt;/code&gt;&lt;/strong&gt; (L155): CLAUDE.md 머지 문자열, 현재 날짜&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;왜 둘로 나누었는가?&lt;/strong&gt; Anthropic API의 프롬프트 캐싱 전략 때문이다. git 상태(세션 고정)와 CLAUDE.md(파일 변경 시에만 무효화)의 캐시 수명이 다르므로, &lt;code&gt;cache_control&lt;/code&gt;을 서로 다르게 적용해야 한다. 두 함수 모두 &lt;code&gt;memoize&lt;/code&gt;로 감싸져 세션 내 한 번만 실행된다.&lt;/p&gt;
&lt;h3 id="3가지-캐시-무효화-경로"&gt;3가지 캐시 무효화 경로
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;setSystemPromptInjection()&lt;/code&gt; (context.ts:29): 양쪽 캐시 모두 클리어&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clearMemoryFileCaches()&lt;/code&gt; (claudemd.ts:1119): 메모리 파일만 클리어&lt;/li&gt;
&lt;li&gt;&lt;code&gt;resetGetMemoryFilesCache()&lt;/code&gt; (claudemd.ts:1124): 메모리 파일 클리어 + &lt;code&gt;InstructionsLoaded&lt;/code&gt; 훅 발화&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 분리는 워크트리 전환(리로드 불필요)과 실제 리로드(compaction 후)를 구분하기 위함이다.&lt;/p&gt;
&lt;h2 id="6-토큰-버짓--응답-연속-결정"&gt;6. 토큰 버짓 &amp;ndash; 응답 연속 결정
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;tokenBudget.ts&lt;/code&gt;(93줄)의 &lt;code&gt;checkTokenBudget()&lt;/code&gt;은 &lt;strong&gt;프롬프트 크기가 아닌 응답 연속 여부&lt;/strong&gt;를 제어한다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;COMPLETION_THRESHOLD = 0.9 -- 90% 미만이면 계속
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DIMINISHING_THRESHOLD = 500 -- 3회+ 연속, 매번 500토큰 미만 -&amp;gt; 수확 체감
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;if (!isDiminishing &amp;amp;&amp;amp; turnTokens &amp;lt; budget * 0.9) -&amp;gt; continue
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;if (isDiminishing || continuationCount &amp;gt; 0) -&amp;gt; stop with event
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;else -&amp;gt; stop without event
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;왜 0.9인가?&lt;/strong&gt; 모델이 버짓 근처에서 요약을 시작하는 경향이 있다. 90%에서 멈추면 &amp;ldquo;마무리 요약&amp;quot;을 하지 않고 작업을 계속하게 만든다. &lt;code&gt;nudgeMessage&lt;/code&gt;가 명시적으로 &amp;ldquo;do not summarize&amp;quot;를 지시한다.&lt;/p&gt;
&lt;p&gt;수확 체감 감지는 모델이 반복적 패턴에 빠지는 것을 방지한다. &lt;strong&gt;서브에이전트는 즉시 stop&lt;/strong&gt; (L51) &amp;ndash; 자체 버짓을 갖지 않는다.&lt;/p&gt;
&lt;h2 id="rust-대조"&gt;Rust 대조
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;관점&lt;/th&gt;
 &lt;th&gt;TS&lt;/th&gt;
 &lt;th&gt;Rust&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;훅 이벤트 유형&lt;/td&gt;
 &lt;td&gt;26개+&lt;/td&gt;
 &lt;td&gt;PreToolUse, PostToolUse 2개&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;훅 실행&lt;/td&gt;
 &lt;td&gt;비동기 AsyncGenerator&lt;/td&gt;
 &lt;td&gt;동기 &lt;code&gt;Command::output()&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;훅 결과&lt;/td&gt;
 &lt;td&gt;7가지 yield 변형 + JSON&lt;/td&gt;
 &lt;td&gt;Allow/Deny/Warn 3가지 (exit code)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;입력 수정&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;hookUpdatedInput&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;불가능&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;allow != bypass&lt;/td&gt;
 &lt;td&gt;보장됨&lt;/td&gt;
 &lt;td&gt;미구현 (보안 취약)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;CLAUDE.md&lt;/td&gt;
 &lt;td&gt;6단계 디스커버리&lt;/td&gt;
 &lt;td&gt;4후보 per dir&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;@include&lt;/td&gt;
 &lt;td&gt;재귀적, 깊이 5&lt;/td&gt;
 &lt;td&gt;미지원&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;토큰 버짓&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;checkTokenBudget()&lt;/code&gt; 3가지 결정&lt;/td&gt;
 &lt;td&gt;없음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;프롬프트 캐시&lt;/td&gt;
 &lt;td&gt;memoize + 3가지 무효화&lt;/td&gt;
 &lt;td&gt;매번 빌드&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="인사이트"&gt;인사이트
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;hook&amp;quot;의 이중 의미가 아키텍처의 가장 큰 혼동원이다&lt;/strong&gt; &amp;ndash; 85개 React 훅은 Rust 재구현 범위에 포함되지 않는다. 런타임 훅(~5,600줄)만이 포팅 대상이다. 그러나 이 런타임 엔진은 26개 이벤트 유형, 비동기 프로토콜(&lt;code&gt;{&amp;quot;async&amp;quot;:true}&lt;/code&gt; 백그라운드 전환), 프롬프트 요청(stdin/stdout 양방향)까지 포함하는 복잡한 시스템이다. &amp;ldquo;훅&amp;quot;이라는 단어의 범위를 정확히 파악하는 것이 범위 산정의 출발점이다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CLAUDE.md의 &amp;ldquo;마지막이 더 강하다&amp;rdquo; 패턴은 LLM 주의 편향의 의도적 활용이다&lt;/strong&gt; &amp;ndash; 6단계 계층 로딩(Managed -&amp;gt; User -&amp;gt; Project -&amp;gt; Local -&amp;gt; AutoMem -&amp;gt; TeamMem)에서 가장 구체적인 지시가 프롬프트 끝에 위치하여 가장 강한 영향력을 갖는다. 이것은 아키텍처적 깔끔함이 아니라 &lt;strong&gt;API 프롬프트 캐싱 적중률 최적화 + LLM 행동 특성&lt;/strong&gt;의 교차점에서 나온 설계다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;resolveHookPermissionDecision()&lt;/code&gt;의 &amp;ldquo;allow != bypass&amp;rdquo; 불변식은 보안의 핵심이다&lt;/strong&gt; &amp;ndash; 현재 Rust hooks.rs는 exit code만으로 allow/deny를 판단한다. JSON 결과 파싱과 &lt;code&gt;checkRuleBasedPermissions&lt;/code&gt; 후속 검사를 구현하지 않으면, 악의적 훅이 deny 규칙을 우회할 수 있는 보안 취약점이 생긴다. 편의 자동화와 보안 정책의 경계를 명확히 하는 것이 훅 시스템의 근본 과제다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;다음 포스트: &lt;a class="link" href="https://ice-ice-bear.github.io/posts/2026-04-06-harness-anatomy-5/" &gt;#5 &amp;ndash; MCP 서비스와 플러그인 스킬 확장 생태계&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Claude Code 하네스 해부학 #5 — MCP 서비스와 플러그인 스킬 확장 생태계</title><link>https://ice-ice-bear.github.io/ko/posts/2026-04-06-harness-anatomy-5/</link><pubDate>Mon, 06 Apr 2026 00:00:00 +0900</pubDate><guid>https://ice-ice-bear.github.io/ko/posts/2026-04-06-harness-anatomy-5/</guid><description>&lt;img src="https://ice-ice-bear.github.io/" alt="Featured image of post Claude Code 하네스 해부학 #5 — MCP 서비스와 플러그인 스킬 확장 생태계" /&gt;&lt;h2 id="개요"&gt;개요
&lt;/h2&gt;&lt;p&gt;Claude Code는 42개의 내장 도구 외에 MCP(Model Context Protocol)를 통해 외부 도구를 무제한으로 확장할 수 있다. 이 포스트에서는 &lt;code&gt;client.ts&lt;/code&gt;(3,348줄)의 연결 관리 아키텍처, &lt;code&gt;auth.ts&lt;/code&gt;(2,465줄)의 OAuth 인증 체계, 4계층 보안 모델, 설정 중복 제거를 분석한다. 이어서 플러그인과 스킬의 구조적 차이, 5계층 스킬 디스커버리 엔진, &lt;code&gt;mcpSkillBuilders.ts&lt;/code&gt;의 순환 참조 해결 패턴까지 해부한다.&lt;/p&gt;
&lt;h2 id="1-mcp-클라이언트--연결-관리가-프로토콜보다-어렵다"&gt;1. MCP 클라이언트 &amp;ndash; 연결 관리가 프로토콜보다 어렵다
&lt;/h2&gt;&lt;h3 id="메모이제이션-기반-커넥션-풀"&gt;메모이제이션 기반 커넥션 풀
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;connectToServer&lt;/code&gt;는 &lt;code&gt;lodash.memoize&lt;/code&gt;로 래핑되어 있다. 캐시 키는 &lt;code&gt;name + JSON(config)&lt;/code&gt;. MCP 서버는 stateful(stdio 프로세스, WebSocket 연결)이므로 매 도구 호출마다 새 연결을 만들면 성능이 치명적으로 나빠진다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;onclose&lt;/code&gt; 핸들러가 캐시를 무효화 -&amp;gt; 다음 호출이 자동으로 재연결&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fetchToolsForClient&lt;/code&gt;, &lt;code&gt;fetchResourcesForClient&lt;/code&gt;도 각각 LRU 캐시(20개)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="도구-프록시-패턴"&gt;도구 프록시 패턴
&lt;/h3&gt;&lt;p&gt;MCP 도구는 네이티브 &lt;code&gt;Tool&lt;/code&gt; 인터페이스로 변환된다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt;: &lt;code&gt;mcp__&amp;lt;normalized_server&amp;gt;__&amp;lt;normalized_tool&amp;gt;&lt;/code&gt; 형식&lt;/li&gt;
&lt;li&gt;&lt;code&gt;call()&lt;/code&gt;: &lt;code&gt;ensureConnectedClient&lt;/code&gt; -&amp;gt; &lt;code&gt;callMCPToolWithUrlElicitationRetry&lt;/code&gt; -&amp;gt; &lt;code&gt;callMCPTool&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;checkPermissions()&lt;/code&gt;: 항상 &lt;code&gt;passthrough&lt;/code&gt; &amp;ndash; MCP 도구는 별도 권한 시스템&lt;/li&gt;
&lt;li&gt;&lt;code&gt;annotations&lt;/code&gt;: &lt;code&gt;readOnlyHint&lt;/code&gt;, &lt;code&gt;destructiveHint&lt;/code&gt; 등 MCP 어노테이션 매핑&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;URL Elicitation Retry&lt;/strong&gt;: OAuth 기반 MCP 서버는 도구 호출 중간에 인증을 요구할 수 있다(에러 코드 -32042). retry 루프가 사용자에게 URL을 보여주고 인증 완료 후 재시도한다.&lt;/p&gt;
&lt;h3 id="연결-상태-머신과-3-strike-터미널-에러"&gt;연결 상태 머신과 3-strike 터미널 에러
&lt;/h3&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;stateDiagram-v2
 [*] --&gt; Pending: 설정 로드됨
 Pending --&gt; Connected: connectToServer 성공
 Pending --&gt; Failed: 연결 타임아웃
 Pending --&gt; NeedsAuth: 401 UnauthorizedError
 Pending --&gt; Disabled: isMcpServerDisabled()

 Connected --&gt; Connected: 도구 호출 성공
 Connected --&gt; Failed: 터미널 에러 3회 연속
 Connected --&gt; NeedsAuth: 401 during callMCPTool
 Connected --&gt; Pending: onclose 캐시 무효화

 NeedsAuth --&gt; Pending: 인증 완료
 NeedsAuth --&gt; NeedsAuth: 15분 TTL 캐시

 Failed --&gt; Pending: reconnectMcpServer()
 Disabled --&gt; Pending: toggleMcpServer()

 note right of Connected
 memoize 캐시에 존재
 fetchTools/Resources도 캐시
 end note&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;3-strike 규칙&lt;/strong&gt;: 터미널 에러가 3회 연속 발생하면 &lt;code&gt;Failed&lt;/code&gt; 상태로 강제 전환. 이는 죽은 서버에 계속 재시도하는 것을 방지한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;15분 needs-auth 캐시&lt;/strong&gt;: 401을 받은 서버를 매번 재시도하면 30개 이상의 커넥터가 동시에 네트워크 요청을 만든다. TTL 캐시로 불필요한 재시도를 방지한다.&lt;/p&gt;
&lt;h2 id="2-oauth--2465줄의-현실"&gt;2. OAuth &amp;ndash; 2,465줄의 현실
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;auth.ts&lt;/code&gt;가 2,465줄인 이유는 &lt;strong&gt;현실의 OAuth 서버들이 RFC를 일관성 있게 구현하지 않기 때문&lt;/strong&gt;이다:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;구성 요소&lt;/th&gt;
 &lt;th&gt;설명&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;RFC 9728 + 8414 디스커버리&lt;/td&gt;
 &lt;td&gt;서버가 별도 호스트에서 AS 운영 가능 -&amp;gt; PRM으로 AS URL 탐색&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;PKCE&lt;/td&gt;
 &lt;td&gt;공개 클라이언트 &amp;ndash; code_verifier/code_challenge 필수&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;XAA (Cross-App Access)&lt;/td&gt;
 &lt;td&gt;IdP의 id_token으로 MCP 서버 AS에서 access_token 교환&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;비표준 에러 정규화&lt;/td&gt;
 &lt;td&gt;Slack은 HTTP 200에 &lt;code&gt;{&amp;quot;error&amp;quot;:&amp;quot;invalid_grant&amp;quot;}&lt;/code&gt; 반환&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Keychain 저장&lt;/td&gt;
 &lt;td&gt;macOS Keychain 연동 (&lt;code&gt;getSecureStorage()&lt;/code&gt;)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Rust 포팅 시사점: OAuth는 SDK 의존이 아니라 &lt;strong&gt;복잡한 비동기 상태 머신&lt;/strong&gt;이다. 디스커버리(2단계) -&amp;gt; PKCE -&amp;gt; 콜백 서버 -&amp;gt; 토큰 저장 -&amp;gt; 갱신 -&amp;gt; 폐기 -&amp;gt; XAA. 이 전체를 이식하는 것은 비현실적이므로, stdio MCP + API 키 인증으로 시작하는 것이 현실적이다.&lt;/p&gt;
&lt;h2 id="3-4계층-보안-모델"&gt;3. 4계층 보안 모델
&lt;/h2&gt;&lt;p&gt;MCP 보안은 단일 게이트가 아니라 &lt;strong&gt;트러스트 레벨의 합성&lt;/strong&gt;이다:&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 subgraph L1["1. Enterprise"]
 E1["managed-mcp.json&amp;lt;br/&amp;gt;존재하면 다른 모든 소스 차단"]
 E2["denylist / allowlist&amp;lt;br/&amp;gt;이름, 명령어, URL 패턴"]
 end

 subgraph L2["2. Project"]
 P1[".mcp.json 로드"]
 P2["pending -&gt; 사용자 승인 -&gt; approved"]
 end

 subgraph L3["3. Server"]
 S1["OAuth 서버별 독립 토큰"]
 S2["Keychain 저장"]
 end

 subgraph L4["4. Channel"]
 C1["GrowthBook allowlist&amp;lt;br/&amp;gt;tengu_harbor_ledger"]
 C2["구조화된 이벤트&amp;lt;br/&amp;gt;일반 텍스트 매칭 아님"]
 end

 L1 --&gt; L2 --&gt; L3 --&gt; L4

 style L1 fill:#ffcdd2
 style L2 fill:#fff9c4
 style L3 fill:#c8e6c9
 style L4 fill:#e1f5fe&lt;/pre&gt;&lt;p&gt;각 레이어가 독립적으로 동작하며, &lt;strong&gt;Enterprise가 최우선&lt;/strong&gt;이다. &lt;code&gt;.mcp.json&lt;/code&gt;이 프로젝트에 있어도 enterprise denylist에 걸리면 차단된다.&lt;/p&gt;
&lt;h3 id="설정-소스와-중복-제거-configts-1578줄"&gt;설정 소스와 중복 제거 (config.ts 1,578줄)
&lt;/h3&gt;&lt;p&gt;설정 소스 우선순위 (높은 것이 이긴다):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Enterprise managed (&lt;code&gt;managed-mcp.json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Local (사용자별 프로젝트 설정)&lt;/li&gt;
&lt;li&gt;User (글로벌 &lt;code&gt;~/.claude.json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Project (&lt;code&gt;.mcp.json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Plugin (동적)&lt;/li&gt;
&lt;li&gt;claude.ai connectors (최저)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;왜 중복 제거가 필요한가?&lt;/strong&gt; 같은 MCP 서버가 &lt;code&gt;.mcp.json&lt;/code&gt;과 claude.ai 커넥터 모두에 존재할 수 있다. &lt;code&gt;getMcpServerSignature&lt;/code&gt;가 &lt;code&gt;stdio:[command|args]&lt;/code&gt; 또는 &lt;code&gt;url:&amp;lt;base&amp;gt;&lt;/code&gt; 시그니처를 만들어, CCR 프록시 URL도 원본 벤더 URL로 언래핑한 뒤 비교한다.&lt;/p&gt;
&lt;p&gt;환경 변수 확장: &lt;code&gt;${VAR}&lt;/code&gt; 및 &lt;code&gt;${VAR:-default}&lt;/code&gt; 문법 지원. 누락 변수는 에러 대신 경고로 보고하여 부분적 연결 실패를 방지한다.&lt;/p&gt;
&lt;h2 id="4-플러그인-vs-스킬--구조적-차이"&gt;4. 플러그인 vs 스킬 &amp;ndash; 구조적 차이
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;차원&lt;/th&gt;
 &lt;th&gt;스킬&lt;/th&gt;
 &lt;th&gt;플러그인&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;본질&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;프롬프트 확장 (SKILL.md = 텍스트)&lt;/td&gt;
 &lt;td&gt;시스템 확장 (스킬 + 훅 + MCP)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;설치&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;파일 하나 배치&lt;/td&gt;
 &lt;td&gt;마켓플레이스 git 클론&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;런타임 코드&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;없음 (순수 텍스트)&lt;/td&gt;
 &lt;td&gt;있음 (MCP 서버, 훅 스크립트)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;토글&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;암묵적 (파일 존재 여부)&lt;/td&gt;
 &lt;td&gt;명시적 (&lt;code&gt;/plugin&lt;/code&gt; UI)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;ID 체계&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;파일 경로&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;{name}@builtin&lt;/code&gt; 또는 &lt;code&gt;{name}@marketplace&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;스킬은 &amp;ldquo;파일 = 확장&amp;quot;이라는 원칙의 구현이다. &lt;code&gt;SKILL.md&lt;/code&gt; 하나가 설치/빌드 없이 즉시 확장으로 작동한다.&lt;/p&gt;
&lt;h3 id="플러그인-서비스의-관심사-분리"&gt;플러그인 서비스의 관심사 분리
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;파일&lt;/th&gt;
 &lt;th&gt;역할&lt;/th&gt;
 &lt;th&gt;부작용&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pluginOperations.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;순수 라이브러리 함수&lt;/td&gt;
 &lt;td&gt;없음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pluginCliCommands.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;CLI 래퍼&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;process.exit&lt;/code&gt;, console 출력&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;PluginInstallationManager.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;백그라운드 조정기&lt;/td&gt;
 &lt;td&gt;AppState 업데이트&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;pluginOperations&lt;/code&gt;의 순수 함수는 CLI와 대화형 UI 양쪽에서 재사용된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;마켓플레이스 조정&lt;/strong&gt;: &lt;code&gt;diffMarketplaces()&lt;/code&gt;로 선언된 마켓플레이스와 실재 설치를 비교. 신규 설치면 자동 새로고침, 기존 업데이트면 &lt;code&gt;needsRefresh&lt;/code&gt; 플래그만 설정. 신규는 &amp;ldquo;플러그인 못 찾음&amp;rdquo; 오류 방지가 필요하지만, 업데이트는 사용자가 적용 시점을 선택해야 한다.&lt;/p&gt;
&lt;h2 id="5-5계층-스킬-디스커버리-엔진"&gt;5. 5계층 스킬 디스커버리 엔진
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;loadSkillsDir.ts&lt;/code&gt;(1,086줄)의 로딩 소스 우선순위:&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 subgraph Discovery["스킬 디스커버리"]
 A["1. policySettings&amp;lt;br/&amp;gt;managed-settings.json"]
 B["2. userSettings&amp;lt;br/&amp;gt;~/.claude/skills/"]
 C["3. projectSettings&amp;lt;br/&amp;gt;.claude/skills/&amp;lt;br/&amp;gt;프로젝트 루트~홈까지"]
 D["4. --add-dir&amp;lt;br/&amp;gt;추가 디렉토리"]
 E["5. 레거시&amp;lt;br/&amp;gt;/commands/ 디렉토리"]
 end

 subgraph Dedup["중복 제거"]
 F["realpath() 심링크 해소"]
 G["파일 ID 기반 first-wins"]
 end

 subgraph Parse["프론트매터 파싱"]
 H["description, when_to_use"]
 I["allowed-tools"]
 J["model, context, hooks"]
 K["paths, shell"]
 end

 A --&gt; B --&gt; C --&gt; D --&gt; E
 E --&gt; F --&gt; G
 G --&gt; H &amp; I &amp; J &amp; K

 style Discovery fill:#e1f5fe
 style Parse fill:#fff3e0&lt;/pre&gt;&lt;h3 id="프론트매터-시스템"&gt;프론트매터 시스템
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;SKILL.md&lt;/code&gt;의 YAML 프론트매터에서 15개+ 필드를 추출한다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;description&lt;/code&gt;, &lt;code&gt;when_to_use&lt;/code&gt;: 모델이 스킬 선택에 사용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;allowed-tools&lt;/code&gt;: 스킬 실행 시 허용 도구 목록&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model&lt;/code&gt;: 특정 모델 강제 지정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;context: fork&lt;/code&gt;: 별도 컨텍스트에서 실행&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hooks&lt;/code&gt;: 스킬 전용 훅 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;paths&lt;/code&gt;: 경로 기반 활성화 필터&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shell&lt;/code&gt;: 인라인 셸 명령 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="번들-스킬의-지연-디스크-추출"&gt;번들 스킬의 지연 디스크 추출
&lt;/h3&gt;&lt;p&gt;CLI 바이너리에 컴파일되는 17개 번들 스킬(&lt;code&gt;skills/bundled/&lt;/code&gt;)은 &lt;code&gt;files&lt;/code&gt; 필드가 있으면 &lt;strong&gt;첫 호출 시 디스크에 추출&lt;/strong&gt;한다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;O_NOFOLLOW | O_EXCL&lt;/code&gt; 플래그로 심링크 공격 차단&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0o600&lt;/code&gt; 퍼미션으로 접근 제한&lt;/li&gt;
&lt;li&gt;&lt;code&gt;resolveSkillFilePath()&lt;/code&gt;가 &lt;code&gt;..&lt;/code&gt; 경로를 거부하여 디렉토리 탈출 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;왜 디스크에 추출하는가?&lt;/strong&gt; 모델이 &lt;code&gt;Read&lt;/code&gt;/&lt;code&gt;Grep&lt;/code&gt; 도구로 참조 파일을 읽을 수 있게 하기 위해서다. 메모리에만 두면 모델이 접근할 수 없다.&lt;/p&gt;
&lt;h3 id="mcpskillbuilders--44줄짜리-순환-참조-해결"&gt;mcpSkillBuilders &amp;ndash; 44줄짜리 순환 참조 해결
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;mcpSkillBuilders.ts&lt;/code&gt;(44줄)는 작지만 아키텍처적으로 중요하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;문제&lt;/strong&gt;: &lt;code&gt;mcpSkills.ts&lt;/code&gt;가 &lt;code&gt;loadSkillsDir.ts&lt;/code&gt;의 함수를 필요로 하지만, 직접 임포트하면 순환 참조가 발생한다 (&lt;code&gt;client.ts -&amp;gt; mcpSkills.ts -&amp;gt; loadSkillsDir.ts -&amp;gt; ... -&amp;gt; client.ts&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해법&lt;/strong&gt;: write-once 레지스트리. &lt;code&gt;loadSkillsDir.ts&lt;/code&gt;가 모듈 초기화 시 함수를 등록하고, &lt;code&gt;mcpSkills.ts&lt;/code&gt;가 필요할 때 가져간다. 동적 임포트는 Bun 번들러에서 실패하고, 리터럴 동적 임포트는 dependency-cruiser 순환 검사를 트리거하기 때문에 &lt;strong&gt;이 방식이 유일한 해결책&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;의존성 그래프의 리프 모듈이 타입만 임포트하고, 런타임 등록은 시작 시 한 번만 수행한다.&lt;/p&gt;
&lt;h2 id="rust-대조"&gt;Rust 대조
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;영역&lt;/th&gt;
 &lt;th&gt;TS (완성)&lt;/th&gt;
 &lt;th&gt;Rust (현재)&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;이름 정규화&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;normalization.ts&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;mcp.rs&lt;/code&gt; &amp;ndash; 동일 로직&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;서버 시그니처&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;getMcpServerSignature&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;mcp_server_signature&lt;/code&gt; &amp;ndash; CCR 프록시 언래핑 포함&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;stdio JSON-RPC&lt;/td&gt;
 &lt;td&gt;SDK 의존&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;mcp_stdio.rs&lt;/code&gt; &amp;ndash; 직접 구현 (initialize, tools/list, tools/call)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;OAuth&lt;/td&gt;
 &lt;td&gt;2,465줄 완전 구현&lt;/td&gt;
 &lt;td&gt;없음 &amp;ndash; 타입만 정의&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;연결 관리&lt;/td&gt;
 &lt;td&gt;memoize + onclose 재연결&lt;/td&gt;
 &lt;td&gt;없음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;스킬 로딩&lt;/td&gt;
 &lt;td&gt;5계층 + 프론트매터 15필드&lt;/td&gt;
 &lt;td&gt;2 디렉토리, SKILL.md만&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;번들 스킬&lt;/td&gt;
 &lt;td&gt;17개 내장&lt;/td&gt;
 &lt;td&gt;없음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;플러그인&lt;/td&gt;
 &lt;td&gt;빌트인 + 마켓플레이스&lt;/td&gt;
 &lt;td&gt;없음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;보안&lt;/td&gt;
 &lt;td&gt;4계층 (Enterprise-&amp;gt;Channel)&lt;/td&gt;
 &lt;td&gt;없음&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;핵심 격차&lt;/strong&gt;: Rust는 부트스트랩(설정 -&amp;gt; 트랜스포트)과 stdio JSON-RPC까지 구현했다. &lt;code&gt;mcp_stdio.rs&lt;/code&gt;의 SDK 없는 JSON-RPC 구현은 의미 있는 진전이다. 그러나 OAuth, 연결 생명주기, 채널 보안, 스킬 디스커버리 전체가 부재한다.&lt;/p&gt;
&lt;h2 id="인사이트"&gt;인사이트
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MCP는 &amp;ldquo;프로토콜&amp;quot;이 아니라 &amp;ldquo;통합 프레임워크&amp;quot;다&lt;/strong&gt; &amp;ndash; 3,348줄의 &lt;code&gt;client.ts&lt;/code&gt;가 말해주는 것은, 어려운 부분이 JSON-RPC가 아니라 &lt;strong&gt;연결 생명주기 관리&lt;/strong&gt;라는 점이다. 메모이제이션, 자동 재연결, 세션 만료 감지, 401 retry, 3-strike 터미널 에러, needs-auth 캐시. 외부 프로세스(stdio)와 원격 서비스(HTTP/SSE)는 예측 불가능하게 죽고, OAuth 토큰은 만료되고, 네트워크는 끊긴다. &amp;ldquo;한번 연결하면 끝&amp;quot;이 아닌 현실을 반영하는 코드다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;스킬은 &amp;ldquo;파일 = 확장&amp;quot;이라는 원칙의 구현이다&lt;/strong&gt; &amp;ndash; SKILL.md 하나가 설치/빌드 없이 즉시 확장으로 작동한다. 이 단순성이 프론트매터를 통한 점진적 복잡성 추가(모델 지정, 훅, 경로 필터)와 결합되어 초보자와 파워 유저를 모두 수용한다. 플러그인은 스킬의 상위 조직 단위로, &amp;ldquo;스킬 + 훅 + MCP 서버&amp;quot;를 묶는 패키지다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;mcpSkillBuilders.ts&lt;/code&gt;는 44줄짜리 아키텍처 교훈이다&lt;/strong&gt; &amp;ndash; Bun 번들러의 동적 임포트 제약과 dependency-cruiser의 순환 검사를 동시에 만족시키는 유일한 해법이 &amp;ldquo;write-once 레지스트리&amp;quot;였다. 의존성 그래프의 리프 모듈이 타입만 임포트하고 런타임 등록은 시작 시 한 번만 수행하는 패턴은, 복잡한 모듈 시스템에서 순환 참조를 해결하는 범용적인 접근법으로 기억할 가치가 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;다음 포스트: &lt;a class="link" href="https://ice-ice-bear.github.io/posts/2026-04-06-harness-anatomy-6/" &gt;#6 &amp;ndash; Claude Code를 넘어서, 7크레이트 독자 하네스 구축 회고&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Claude Code 하네스 해부학 #6 — Claude Code를 넘어서, 7크레이트 독자 하네스 구축 회고</title><link>https://ice-ice-bear.github.io/ko/posts/2026-04-06-harness-anatomy-6/</link><pubDate>Mon, 06 Apr 2026 00:00:00 +0900</pubDate><guid>https://ice-ice-bear.github.io/ko/posts/2026-04-06-harness-anatomy-6/</guid><description>&lt;img src="https://ice-ice-bear.github.io/" alt="Featured image of post Claude Code 하네스 해부학 #6 — Claude Code를 넘어서, 7크레이트 독자 하네스 구축 회고" /&gt;&lt;h2 id="개요"&gt;개요
&lt;/h2&gt;&lt;p&gt;Claude Code의 TypeScript 소스를 27세션에 걸쳐 체계적으로 해부한 여정의 마지막 포스트다. Phase 1에서 10만줄+ TS 코드의 아키텍처를 이해하고, Phase 2에서 핵심 패턴을 Rust로 재구현한 뒤, Phase 3에서 발견한 8가지 한계점을 극복하는 독자 에이전트 하네스를 설계-구축했다. 이 포스트에서는 한계점 분석, 5가지 설계 원칙, 7크레이트 아키텍처, 61개 테스트, 그리고 전체 여정의 회고를 정리한다.&lt;/p&gt;
&lt;h2 id="1-claude-code-아키텍처의-8가지-한계점"&gt;1. Claude Code 아키텍처의 8가지 한계점
&lt;/h2&gt;&lt;p&gt;27세션의 분석에서 발견한 강점과 한계를 구분했다. 강점(AsyncGenerator 파이프라인, 3-tier 동시성, 훅 확장성, CLAUDE.md 디스커버리, MCP 지원, 자기 완결적 도구 인터페이스, 7가지 에러 복구)은 설계의 우수한 면이다. 그러나 다음 8가지 한계가 독자 하네스의 동기가 되었다:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;#&lt;/th&gt;
 &lt;th&gt;한계점&lt;/th&gt;
 &lt;th&gt;근거 세션&lt;/th&gt;
 &lt;th&gt;영향&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;React/Ink 의존 &amp;ndash; 무거운 TUI&lt;/td&gt;
 &lt;td&gt;S08&lt;/td&gt;
 &lt;td&gt;headless 모드에서 불필요한 의존&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;단일 프로바이더 (실질 Anthropic 전용)&lt;/td&gt;
 &lt;td&gt;S01&lt;/td&gt;
 &lt;td&gt;OpenAI, 로컬 모델 사용 불가&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;main.tsx 4,683줄 모놀리스&lt;/td&gt;
 &lt;td&gt;S01&lt;/td&gt;
 &lt;td&gt;CLI/REPL/세션이 단일 파일에 혼재&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;동기적 도구 실행 (Rust 포트)&lt;/td&gt;
 &lt;td&gt;S03&lt;/td&gt;
 &lt;td&gt;스트리밍 파이프라이닝 불가&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;td&gt;TS 생태계에 묶인 플러그인&lt;/td&gt;
 &lt;td&gt;S13&lt;/td&gt;
 &lt;td&gt;언어 중립적 확장 불가&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;6&lt;/td&gt;
 &lt;td&gt;85개 React 훅의 UI/런타임 혼재&lt;/td&gt;
 &lt;td&gt;S08&lt;/td&gt;
 &lt;td&gt;&amp;ldquo;hook&amp;rdquo; 용어의 이중 의미&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;7&lt;/td&gt;
 &lt;td&gt;프롬프트 캐싱의 암묵적 의존&lt;/td&gt;
 &lt;td&gt;S10&lt;/td&gt;
 &lt;td&gt;3가지 캐시 무효화 경로가 암묵적&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;8&lt;/td&gt;
 &lt;td&gt;MCP OAuth 2,465줄의 복잡성&lt;/td&gt;
 &lt;td&gt;S12&lt;/td&gt;
 &lt;td&gt;RFC 비일관성이 근본 원인&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="2-5가지-설계-원칙"&gt;2. 5가지 설계 원칙
&lt;/h2&gt;&lt;p&gt;한계점을 극복하기 위해 5가지 핵심 원칙을 정립했다:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원칙 1 &amp;ndash; 멀티 프로바이더&lt;/strong&gt;: Anthropic, OpenAI, 로컬 모델(Ollama)을 단일 추상화로 지원.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#[async_trait]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;trait&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Provider&lt;/span&gt;: &lt;span class="nb"&gt;Send&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Sync&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;: &lt;span class="nc"&gt;ProviderRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EventStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ProviderError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;available_models&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ModelInfo&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;ProviderRequest&lt;/code&gt;는 프로바이더 중립적 구조체로, 각 구현체가 자신의 API 형식으로 변환한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원칙 2 &amp;ndash; 네이티브 비동기&lt;/strong&gt;: tokio 기반 완전 비동기. &lt;code&gt;yield&lt;/code&gt; -&amp;gt; &lt;code&gt;tx.send()&lt;/code&gt;, &lt;code&gt;yield*&lt;/code&gt; -&amp;gt; 채널 전달로 AsyncGenerator 패턴을 대체.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원칙 3 &amp;ndash; 모듈 분리&lt;/strong&gt;: 대화 엔진, 도구, 훅, 프롬프트를 각각 독립 크레이트로 분리. &lt;code&gt;main.tsx&lt;/code&gt; 모놀리스를 반복하지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원칙 4 &amp;ndash; 언어 중립 확장&lt;/strong&gt;: SKILL.md 호환 + MCP 서버를 플러그인 단위로 활용.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원칙 5 &amp;ndash; MCP 통합 활용&lt;/strong&gt;: 도구뿐 아니라 리소스, 프롬프트, 샘플링까지 전체 스펙 활용.&lt;/p&gt;
&lt;h2 id="3-7크레이트-아키텍처"&gt;3. 7크레이트 아키텍처
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;graph TD
 CLI["harness-cli&amp;lt;br/&amp;gt;REPL 바이너리"] --&gt; CORE["harness-core&amp;lt;br/&amp;gt;대화 엔진 + 턴 루프"]
 CORE --&gt; PROV["harness-provider&amp;lt;br/&amp;gt;LLM 프로바이더 추상화"]
 CORE --&gt; TOOLS["harness-tools&amp;lt;br/&amp;gt;도구 레지스트리 + 내장 도구"]
 CORE --&gt; HOOKS["harness-hooks&amp;lt;br/&amp;gt;훅 파이프라인"]
 CORE --&gt; PROMPT["harness-prompt&amp;lt;br/&amp;gt;CLAUDE.md 디스커버리"]
 CORE --&gt; MCP["harness-mcp&amp;lt;br/&amp;gt;MCP 클라이언트"]
 MCP --&gt; TOOLS

 style CLI fill:#b3e5fc
 style CORE fill:#fff9c4
 style PROV fill:#c8e6c9
 style TOOLS fill:#c8e6c9
 style HOOKS fill:#c8e6c9
 style PROMPT fill:#c8e6c9
 style MCP fill:#e1bee7&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;핵심 설계&lt;/strong&gt;: &lt;code&gt;harness-core&lt;/code&gt;만 다른 크레이트를 의존한다. 나머지는 서로 독립적이다(&lt;code&gt;harness-mcp&lt;/code&gt; -&amp;gt; &lt;code&gt;harness-tools&lt;/code&gt; 제외). 이 구조 덕분에:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;각 크레이트를 독립적으로 &lt;code&gt;cargo test&lt;/code&gt; 가능&lt;/li&gt;
&lt;li&gt;프로바이더 추가 시 &lt;code&gt;harness-core&lt;/code&gt; 수정 불필요&lt;/li&gt;
&lt;li&gt;MCP 도구가 내장 도구와 동일한 &lt;code&gt;Tool&lt;/code&gt; 트레이트 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;크레이트&lt;/th&gt;
 &lt;th&gt;핵심 책임&lt;/th&gt;
 &lt;th&gt;테스트 수&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;harness-provider&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;LLM API 호출, SSE 파싱, 재시도&lt;/td&gt;
 &lt;td&gt;11&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;harness-tools&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;도구 레지스트리, 3-tier 동시성&lt;/td&gt;
 &lt;td&gt;12&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;harness-hooks&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;셸 훅 실행, deny short-circuit, rewrite 체인&lt;/td&gt;
 &lt;td&gt;9&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;harness-prompt&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;6단계 CLAUDE.md, SHA-256 중복 제거&lt;/td&gt;
 &lt;td&gt;9&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;harness-core&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;대화 엔진, &lt;code&gt;StreamingToolExecutor&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;6&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;harness-mcp&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;JSON-RPC, stdio 트랜스포트&lt;/td&gt;
 &lt;td&gt;14&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;harness-cli&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;REPL 바이너리&lt;/td&gt;
 &lt;td&gt;&amp;ndash;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="provider-트레이트--멀티-프로바이더"&gt;Provider 트레이트 &amp;ndash; 멀티 프로바이더
&lt;/h3&gt;&lt;p&gt;기존 Rust 포트의 &lt;code&gt;ApiClient&lt;/code&gt; 트레이트는 Anthropic 전용이었다(&lt;code&gt;ApiRequest&lt;/code&gt;에 Anthropic 필드). &lt;code&gt;Provider&lt;/code&gt; 트레이트는 프로바이더 중립적 &lt;code&gt;ProviderRequest&lt;/code&gt;를 받아 각 구현체가 자신의 API 형식으로 변환한다. &lt;code&gt;Box&amp;lt;dyn Provider&amp;gt;&lt;/code&gt;로 런타임에 폴백 체인 구현 가능.&lt;/p&gt;
&lt;h3 id="conversationengine--턴-루프"&gt;ConversationEngine &amp;ndash; 턴 루프
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;ConversationEngine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;: &lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;: &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tool_executor&lt;/span&gt;: &lt;span class="nc"&gt;StreamingToolExecutor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hook_pipeline&lt;/span&gt;: &lt;span class="nc"&gt;HookPipeline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prompt_builder&lt;/span&gt;: &lt;span class="nc"&gt;PromptBuilder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;budget&lt;/span&gt;: &lt;span class="nc"&gt;TokenBudget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;기존 Rust 포트의 &lt;code&gt;ConversationRuntime&amp;lt;C, T&amp;gt;&lt;/code&gt; 제네릭 패턴 대신 트레이트 객체를 사용한다. 런타임에 프로바이더를 교체할 수 있어야 하고(모델 폴백), 제네릭은 컴파일 타임에 타입이 고정되므로 유연성이 부족하다.&lt;/p&gt;
&lt;h3 id="스트리밍-도구-실행-파이프라이닝"&gt;스트리밍 도구 실행 (파이프라이닝)
&lt;/h3&gt;&lt;p&gt;기존 Rust 포트의 가장 큰 제약인 &amp;ldquo;모든 SSE 이벤트를 수집 후 도구 실행&amp;quot;을 해결했다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;EventStream&lt;/code&gt;에서 &lt;code&gt;ContentBlockStop(ToolUse)&lt;/code&gt; 이벤트 도착 시 즉시 전달&lt;/li&gt;
&lt;li&gt;&lt;code&gt;is_concurrency_safe()&lt;/code&gt; 검사 후 &lt;code&gt;tokio::spawn&lt;/code&gt;으로 병렬 처리&lt;/li&gt;
&lt;li&gt;API가 아직 스트리밍 중인 동안 도구 실행 병행&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="4-phase-2-회고--기존-포트-확장"&gt;4. Phase 2 회고 &amp;ndash; 기존 포트 확장
&lt;/h2&gt;&lt;p&gt;Phase 3의 독자 하네스 이전에, Phase 2에서 기존 &lt;code&gt;rust/&lt;/code&gt; 프로토타입을 확장했다:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;스프린트&lt;/th&gt;
 &lt;th&gt;성과&lt;/th&gt;
 &lt;th&gt;핵심 패턴&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;S14-S15&lt;/td&gt;
 &lt;td&gt;오케스트레이션 모듈 + 3-tier 동시성&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tokio::JoinSet&lt;/code&gt; 기반 병렬 실행&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;S16-S17&lt;/td&gt;
 &lt;td&gt;도구 확장 (19 -&amp;gt; 26개)&lt;/td&gt;
 &lt;td&gt;Task, PlanMode, AskUser 추가&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;S18-S19&lt;/td&gt;
 &lt;td&gt;훅 실행 파이프라인&lt;/td&gt;
 &lt;td&gt;stdin JSON, deny short-circuit&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;S20-S21&lt;/td&gt;
 &lt;td&gt;스킬 디스커버리&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;.claude/skills/&lt;/code&gt; 스캔, 프롬프트 주입&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Phase 2의 코드 대부분은 Phase 3에서 재작성되었다. 그러나 &lt;strong&gt;프로토타입 과정에서 발견한 질문들&lt;/strong&gt;(&amp;ldquo;왜 AsyncGenerator인가?&amp;rdquo;, &amp;ldquo;왜 도구가 UI를 몰라야 하는가?&amp;quot;)이 최종 설계를 결정했다.&lt;/p&gt;
&lt;h2 id="5-61개-테스트와-mockprovider-패턴"&gt;5. 61개 테스트와 MockProvider 패턴
&lt;/h2&gt;&lt;p&gt;모든 크레이트가 독립적으로 테스트 가능하다. &lt;code&gt;MockProvider&lt;/code&gt;를 통해 실제 API 호출 없이 대화 엔진의 전체 턴 루프를 검증한다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;harness&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="err"&gt;테스트&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SSE&lt;/span&gt; &lt;span class="err"&gt;파싱&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;재시도&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;스트림&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;harness&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="err"&gt;테스트&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;레지스트리&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;동시성&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;실행&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;harness&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="err"&gt;테스트&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deny&lt;/span&gt; &lt;span class="err"&gt;단락&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rewrite&lt;/span&gt; &lt;span class="err"&gt;체인&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;타임아웃&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;harness&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="err"&gt;테스트&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="err"&gt;단계&lt;/span&gt; &lt;span class="err"&gt;디스커버리&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;해시&lt;/span&gt; &lt;span class="err"&gt;중복제거&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;harness&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="err"&gt;테스트&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;턴&lt;/span&gt; &lt;span class="err"&gt;루프&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;도구&lt;/span&gt; &lt;span class="err"&gt;호출&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;최대&lt;/span&gt; &lt;span class="err"&gt;반복&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;harness&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt; &lt;span class="err"&gt;테스트&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;RPC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;초기화&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;도구&lt;/span&gt; &lt;span class="err"&gt;목록&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="6-phase-1-2-교훈이-설계에-반영된-방식"&gt;6. Phase 1-2 교훈이 설계에 반영된 방식
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 subgraph Phase1["Phase 1 -- 이해"]
 direction TB
 P1A["S02: AsyncGenerator 체인"]
 P1B["S05: 42개 도구 분류"]
 P1C["S08: 런타임 vs React 훅"]
 P1D["S10: 6단계 CLAUDE.md"]
 P1E["S12: MCP 연결 관리"]
 P1F["S13: 스킬 = 프롬프트"]
 end

 subgraph Phase3["Phase 3 -- 독자 하네스"]
 direction TB
 P3A["EventStream + mpsc 채널"]
 P3B["Tool 트레이트 + 3-tier"]
 P3C["HookPipeline (런타임만)"]
 P3D["PromptAssembler 분리"]
 P3E["harness-mcp stdio"]
 P3F["SKILL.md 호환"]
 end

 P1A --&gt;|"yield -&gt; tx.send()"| P3A
 P1B --&gt;|"fail-closed 기본값"| P3B
 P1C --&gt;|"범위 축소"| P3C
 P1D --&gt;|"캐시 분할"| P3D
 P1E --&gt;|"SDK 없이 구현"| P3E
 P1F --&gt;|"텍스트 주입"| P3F

 style Phase1 fill:#e1f5fe
 style Phase3 fill:#fff3e0&lt;/pre&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;교훈&lt;/th&gt;
 &lt;th&gt;출처&lt;/th&gt;
 &lt;th&gt;설계 반영&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;StreamingToolExecutor&lt;/code&gt; 4단계 상태 기계&lt;/td&gt;
 &lt;td&gt;S03&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;harness-core&lt;/code&gt;에 async 구현&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;QueryDeps&lt;/code&gt; 콜백 DI의 타입 안전성 한계&lt;/td&gt;
 &lt;td&gt;S03&lt;/td&gt;
 &lt;td&gt;트레이트 객체 DI&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;6층 Bash 보안 체인&lt;/td&gt;
 &lt;td&gt;S06&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;check_permissions()&lt;/code&gt; + 훅 분리&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;에이전트 = 하네스 재귀 인스턴스&lt;/td&gt;
 &lt;td&gt;S06&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;ConversationEngine&lt;/code&gt; 재사용&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;ApiClient&lt;/code&gt; 동기 트레이트가 파이프라이닝 차단&lt;/td&gt;
 &lt;td&gt;S03&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;Provider&lt;/code&gt; async 트레이트&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Deny 단락 + Rewrite 체이닝&lt;/td&gt;
 &lt;td&gt;S09&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;HookPipeline&lt;/code&gt; 동일 패턴&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SHA-256 콘텐츠 해시가 경로 해시보다 우수&lt;/td&gt;
 &lt;td&gt;S11&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;harness-prompt&lt;/code&gt; 콘텐츠 해시&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="7-학습한-아키텍처-패턴-top-10"&gt;7. 학습한 아키텍처 패턴 TOP 10
&lt;/h2&gt;&lt;p&gt;27세션에서 추출한 핵심 아키텍처 패턴:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;AsyncGenerator/Stream 파이프라인&lt;/strong&gt;: 스트리밍 LLM 응답의 핵심 추상화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3-tier 도구 동시성&lt;/strong&gt;: ReadOnly/Write/Dangerous 분류로 안전성과 성능 균형&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ToolSpec + ToolResult 이원화&lt;/strong&gt;: 메타데이터(LLM 전달용)와 실행 결과 분리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;훅 체인 실행&lt;/strong&gt;: deny short-circuit, rewrite 체인, 독립 post-hook 변환&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;6단계 프롬프트 디스커버리&lt;/strong&gt;: managed -&amp;gt; user -&amp;gt; project -&amp;gt; local 오버라이드&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP 어댑터 패턴&lt;/strong&gt;: 외부 프로토콜 도구를 내부 Tool 트레이트로 통일&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Provider 추상화&lt;/strong&gt;: 동일 인터페이스로 Anthropic/OpenAI 교체 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSE 점진적 파싱&lt;/strong&gt;: 네트워크 청크를 이벤트 프레임으로 조립&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MockProvider 테스트&lt;/strong&gt;: 사전 정의 이벤트 시퀀스로 엔진 동작 검증&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;스킬 = 프롬프트&lt;/strong&gt;: 복잡한 플러그인 시스템 대신 텍스트 주입으로 충분&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="8-전체-여정-회고"&gt;8. 전체 여정 회고
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Phase&lt;/th&gt;
 &lt;th&gt;세션&lt;/th&gt;
 &lt;th&gt;핵심 산출물&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Phase 1 &amp;ndash; 이해&lt;/td&gt;
 &lt;td&gt;S00-S13&lt;/td&gt;
 &lt;td&gt;14개 분석 문서, Rust 프로토타입&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Phase 2 &amp;ndash; 재구현&lt;/td&gt;
 &lt;td&gt;S14-S21&lt;/td&gt;
 &lt;td&gt;오케스트레이션, 26 도구, 훅, 스킬&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Phase 3 &amp;ndash; 독자 하네스&lt;/td&gt;
 &lt;td&gt;S22-S27&lt;/td&gt;
 &lt;td&gt;7크레이트 워크스페이스, 61+ 테스트&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Claude Code는 &lt;strong&gt;프롬프트 엔지니어링 런타임&lt;/strong&gt;이다. 핵심 루프가 메시지를 조립하고, 도구 시스템이 세상과의 상호작용 능력을 부여하고, 권한 시스템이 경계를 설정한다. CLAUDE.md가 컨텍스트를 주입하고, MCP가 외부를 통합하며, 훅과 에이전트가 자동화/위임을 가능케 하고, 플러그인/스킬이 사용자 확장 플랫폼으로 전환한다.&lt;/p&gt;
&lt;h3 id="향후-방향"&gt;향후 방향
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;진정한 스트리밍: SSE 바이트 스트림을 청크 단위로 처리&lt;/li&gt;
&lt;li&gt;권한 시스템: 도구별 사용자 승인 워크플로우&lt;/li&gt;
&lt;li&gt;MCP SSE 전송: stdio 외에 HTTP SSE 지원&lt;/li&gt;
&lt;li&gt;토큰 예산 통합: 컨텍스트 윈도우 예산 자동 관리&lt;/li&gt;
&lt;li&gt;멀티턴 에이전트 모드: 자율 반복 + 중단점 시스템&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="인사이트"&gt;인사이트
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;좋은 추상화는 경계에서 나온다&lt;/strong&gt; &amp;ndash; Provider 트레이트, Tool 트레이트, HookRunner 트레이트. 모든 핵심 추상화는 모듈 간 경계를 정의하는 트레이트다. 기존 Rust 포트의 &lt;code&gt;ConversationRuntime&amp;lt;C, T&amp;gt;&lt;/code&gt; 제네릭은 컴파일 타임 보장이 강하지만, 런타임에 프로바이더를 교체하거나 MCP 도구를 동적 등록하는 시나리오에서 한계가 있었다. &lt;code&gt;Box&amp;lt;dyn Provider&amp;gt;&lt;/code&gt; + &lt;code&gt;Box&amp;lt;dyn Tool&amp;gt;&lt;/code&gt; 트레이트 객체가 약간의 vtable 비용으로 런타임 유연성을 확보한다. LLM API 지연시간(수백 ms~수 s) 대비 vtable 비용은 측정 불가능한 수준이다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;프로토타입의 가치는 코드가 아니라 질문이다&lt;/strong&gt; &amp;ndash; Phase 1-2의 프로토타입 코드 대부분은 Phase 3에서 재작성되었다. 그러나 &amp;ldquo;왜 AsyncGenerator인가?&amp;rdquo;, &amp;ldquo;왜 도구가 UI를 몰라야 하는가?&amp;rdquo;, &amp;ldquo;왜 allow가 bypass하지 않는가?&amp;rdquo; 같은 질문들이 최종 설계를 결정했다. 10만줄 코드를 읽는 행위 자체가 답이 아니라, 읽으면서 발견하는 **설계 의도(왜)**가 진정한 산출물이다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TS 코드의 복잡성은 대부분 방어선이다&lt;/strong&gt; &amp;ndash; 권한 계층, 프론트매터 파싱, 중복 제거, symlink 방지. 이것들은 기능이 아니라 방어선이다. Rust는 타입 시스템과 소유권 모델로 일부를 컴파일타임에 보장할 수 있지만, 파일시스템 보안과 사용자 설정 우선순위 같은 런타임 정책은 명시적으로 구현해야 한다. 27세션은 이 방어선의 지도를 그리는 과정이었고, 그 지도가 독자 하네스의 설계를 안내했다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;시리즈 완결. 전체 분석 문서는 &lt;a class="link" href="https://github.com/lsr/claw-code" target="_blank" rel="noopener"
 &gt;claw-code 리포지토리&lt;/a&gt;에서 확인할 수 있다.&lt;/em&gt;&lt;/p&gt;</description></item></channel></rss>