<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Concurrency on ICE-ICE-BEAR-BLOG</title><link>https://ice-ice-bear.github.io/ko/tags/concurrency/</link><description>Recent content in Concurrency 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/tags/concurrency/index.xml" rel="self" type="application/rss+xml"/><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></channel></rss>