<?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/categories/harness-engineering/</link><description>Recent content in Harness-Engineering on ICE-ICE-BEAR-BLOG</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 06 Apr 2026 00:00:00 +0900</lastBuildDate><atom:link href="https://ice-ice-bear.github.io/categories/harness-engineering/index.xml" rel="self" type="application/rss+xml"/><item><title>Claude Code Harness Anatomy #1 — From Entry Point to Response: The Journey of a Single Request</title><link>https://ice-ice-bear.github.io/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/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 Harness Anatomy #1 — From Entry Point to Response: The Journey of a Single Request" /&gt;&lt;h2 id="overview"&gt;Overview
&lt;/h2&gt;&lt;p&gt;This is the first post in a series that systematically dissects Claude Code&amp;rsquo;s source structure across 27 sessions. In this post, we trace the &lt;strong&gt;complete call stack across 11 TypeScript files&lt;/strong&gt; that a &amp;ldquo;hello&amp;rdquo; typed into the terminal traverses before a response appears on screen.&lt;/p&gt;
&lt;h2 id="analysis-target-11-core-files"&gt;Analysis Target: 11 Core Files
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;#&lt;/th&gt;
 &lt;th&gt;Path&lt;/th&gt;
 &lt;th&gt;Lines&lt;/th&gt;
 &lt;th&gt;Role&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 bootstrap, argument parsing, mode routing&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;Main REPL component, Commander setup&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;Command registry&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;System prompt assembly, CLAUDE.md injection&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;Session management, SDK interface&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;Core turn loop&lt;/strong&gt; — API + tool execution&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 client, 4-provider routing&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 wrapper, SSE streaming, retries&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;Concurrency partitioning&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;Tool execution during streaming&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;Tool dispatch, permission checks&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;We trace a total of &lt;strong&gt;15,223 lines&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="1-entry-and-bootstrap-clitsx---maintsx"&gt;1. Entry and Bootstrap: cli.tsx -&amp;gt; main.tsx
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;cli.tsx&lt;/code&gt; is only 302 lines, yet it contains a surprising number of &lt;strong&gt;fast-path&lt;/strong&gt; branches:&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; immediate output, 0 imports
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cli.tsx:53 --dump-system -&amp;gt; minimal imports
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cli.tsx:100 --daemon-worker -&amp;gt; worker-only path
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cli.tsx:112 remote-control -&amp;gt; bridge mode
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cli.tsx:185 ps/logs/attach -&amp;gt; background sessions
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cli.tsx:293 default path -&amp;gt; dynamic import of main.tsx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Design intent&lt;/strong&gt;: Avoid loading &lt;code&gt;main.tsx&lt;/code&gt;&amp;rsquo;s 4,683 lines just for &lt;code&gt;--version&lt;/code&gt;. This optimization directly impacts the perceived responsiveness of the CLI tool.&lt;/p&gt;
&lt;p&gt;The default path dynamically imports &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;The reason &lt;code&gt;main.tsx&lt;/code&gt; is 4,683 lines is that it includes all of the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Side-effect imports&lt;/strong&gt; (lines 1-209): &lt;code&gt;profileCheckpoint&lt;/code&gt;, &lt;code&gt;startMdmRawRead&lt;/code&gt;, &lt;code&gt;startKeychainPrefetch&lt;/code&gt; — parallel subprocesses launched at module evaluation time to hide the ~65ms macOS keychain read&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commander setup&lt;/strong&gt; (line 585+): CLI argument parsing, 10+ mode-specific branches&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;React/Ink REPL rendering&lt;/strong&gt;: Terminal UI mount&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Headless path&lt;/strong&gt; (&lt;code&gt;-p&lt;/code&gt;/&lt;code&gt;--print&lt;/code&gt;): Uses &lt;code&gt;QueryEngine&lt;/code&gt; directly without UI&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="2-prompt-assembly-contexttss-dual-memoize"&gt;2. Prompt Assembly: context.ts&amp;rsquo;s dual-memoize
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;context.ts&lt;/code&gt; is a small file at 189 lines, but it handles all dynamic parts of the system prompt. Two memoized functions are at its core:&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): Collects git state (branch, status, recent commits)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;getUserContext()&lt;/code&gt;&lt;/strong&gt; (context.ts:155): Discovers and parses CLAUDE.md files&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Why the separation?&lt;/strong&gt; It&amp;rsquo;s directly tied to the Anthropic Messages API&amp;rsquo;s prompt caching strategy. Since the cache lifetimes of the system prompt and user context differ, &lt;code&gt;cache_control&lt;/code&gt; must be applied differently to each. Wrapping them in &lt;code&gt;memoize&lt;/code&gt; ensures each is computed only once per session.&lt;/p&gt;
&lt;p&gt;The call to &lt;code&gt;setCachedClaudeMdContent()&lt;/code&gt; at context.ts:170-176 is &lt;strong&gt;a mechanism to break circular dependencies&lt;/strong&gt; — yoloClassifier needs CLAUDE.md content, but a direct import would create a permissions -&amp;gt; yoloClassifier -&amp;gt; claudemd -&amp;gt; permissions cycle.&lt;/p&gt;
&lt;h2 id="3-asyncgenerator-chain-the-architectural-spine"&gt;3. AsyncGenerator Chain: The Architectural Spine
&lt;/h2&gt;&lt;p&gt;Claude Code&amp;rsquo;s entire data flow is built on an &lt;code&gt;AsyncGenerator&lt;/code&gt; chain:&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;Every core function is an &lt;code&gt;async function*&lt;/code&gt;. This isn&amp;rsquo;t just an implementation choice — it&amp;rsquo;s an &lt;strong&gt;architectural decision&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Backpressure&lt;/strong&gt;: When the consumer is slow, the producer waits&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cancellation&lt;/strong&gt;: Combined with AbortController for immediate cancellation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Composition&lt;/strong&gt;: &lt;code&gt;yield*&lt;/code&gt; naturally chains generators together&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State management&lt;/strong&gt;: Local variables within loops naturally maintain state across turns&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Looking at the signature of &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;In SDK mode, each message is &lt;strong&gt;streamed via yield&lt;/strong&gt;, and Node.js backpressure is naturally implemented.&lt;/p&gt;
&lt;h2 id="4-the-core-turn-loop-querytss-whiletrue"&gt;4. The Core Turn Loop: query.ts&amp;rsquo;s while(true)
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;queryLoop()&lt;/code&gt; in &lt;code&gt;query.ts&lt;/code&gt; (1,729 lines) is the actual API + tool loop:&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. Call queryModelWithStreaming() -&amp;gt; SSE stream
&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 streaming events
&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. Detect tool calls -&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. Append tool results to messages
&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;The &lt;code&gt;State&lt;/code&gt; type (query.ts:204) is important. It manages loop state as an explicit record with fields like &lt;code&gt;messages&lt;/code&gt;, &lt;code&gt;toolUseContext&lt;/code&gt;, &lt;code&gt;autoCompactTracking&lt;/code&gt;, and &lt;code&gt;maxOutputTokensRecoveryCount&lt;/code&gt;, updating everything at once at continue sites.&lt;/p&gt;
&lt;h2 id="5-api-communication-4-providers-and-caching"&gt;5. API Communication: 4 Providers and Caching
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;getAnthropicClient()&lt;/code&gt; at &lt;code&gt;client.ts:88&lt;/code&gt; supports 4 providers:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Provider&lt;/th&gt;
 &lt;th&gt;SDK&lt;/th&gt;
 &lt;th&gt;Reason for Dynamic 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;Default, loaded immediately&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 is several 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 is several 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 is several MB&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The core function chain in &lt;code&gt;claude.ts&lt;/code&gt; (3,419 lines):&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 call)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The caching strategy is determined by &lt;code&gt;getCacheControl()&lt;/code&gt; (claude.ts:358), which decides the 1-hour TTL based on user type, feature flags, and query source.&lt;/p&gt;
&lt;h2 id="6-tool-orchestration-3-tier-concurrency"&gt;6. Tool Orchestration: 3-Tier Concurrency
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 TC["Tool call array&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;max 10 concurrent"]
 SEQ["Sequential execution"]
 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 lines) extends this batch partitioning into a &lt;strong&gt;streaming context&lt;/strong&gt;. When it detects tool calls while the API response is still streaming, it immediately starts execution:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;addTool()&lt;/code&gt; (StreamingToolExecutor.ts:76) — Add to queue&lt;/li&gt;
&lt;li&gt;&lt;code&gt;processQueue()&lt;/code&gt; (StreamingToolExecutor.ts:140) — Check concurrency, then execute immediately&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getRemainingResults()&lt;/code&gt; (StreamingToolExecutor.ts:453) — Wait for all tools to complete&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Error propagation rules&lt;/strong&gt;: Only Bash errors cancel sibling tools (&lt;code&gt;siblingAbortController&lt;/code&gt;). Read/WebFetch errors don&amp;rsquo;t affect other tools. This reflects the implicit dependencies between Bash commands (if mkdir fails, subsequent commands are pointless).&lt;/p&gt;
&lt;h2 id="full-data-flow"&gt;Full Data Flow
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;sequenceDiagram
 participant User as User
 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: Types "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 stream

 alt stop_reason == end_turn
 Claude--&gt;&gt;User: Output response
 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: Tool results
 Note over Query: Next iteration of while(true)
 end&lt;/pre&gt;&lt;h2 id="rust-gap-map-preview"&gt;Rust Gap Map Preview
&lt;/h2&gt;&lt;p&gt;Tracing the same request through the Rust port revealed &lt;strong&gt;31 gaps&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Priority&lt;/th&gt;
 &lt;th&gt;Gap Count&lt;/th&gt;
 &lt;th&gt;Key Examples&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;P0 (Critical)&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;Synchronous ApiClient, missing StreamingToolExecutor&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P1 (High)&lt;/td&gt;
 &lt;td&gt;6&lt;/td&gt;
 &lt;td&gt;3-tier concurrency, prompt caching, Agent tool&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P2 (Medium)&lt;/td&gt;
 &lt;td&gt;7&lt;/td&gt;
 &lt;td&gt;Multi-provider, effort control, sandbox&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Implemented&lt;/td&gt;
 &lt;td&gt;11&lt;/td&gt;
 &lt;td&gt;Auto-compaction, SSE parser, OAuth, config loading&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Implementation coverage: 36% (11/31)&lt;/strong&gt;. The next post dives deep into the conversation loop at the heart of these gaps.&lt;/p&gt;
&lt;h2 id="insights"&gt;Insights
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AsyncGenerator is the architectural spine&lt;/strong&gt; — It&amp;rsquo;s not just an implementation technique but a design decision that simultaneously solves backpressure, cancellation, and composition. In Rust, the &lt;code&gt;Stream&lt;/code&gt; trait is the counterpart, but the ergonomics of &lt;code&gt;yield*&lt;/code&gt; composition differ significantly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;main.tsx at 4,683 lines is technical debt&lt;/strong&gt; — Commander setup, React components, and state management are all mixed in a single file. This is the result of organic growth and represents an opportunity for module decomposition.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tool concurrency is non-trivial&lt;/strong&gt; — The 3-tier model (read batches, sequential writes, Bash sibling cancellation) rather than &amp;ldquo;all parallel&amp;rdquo; or &amp;ldquo;all sequential&amp;rdquo; is a core design element of production agent harnesses.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Next post: &lt;a class="link" href="https://ice-ice-bear.github.io/posts/2026-04-06-harness-anatomy-2/" &gt;#2 — The Heart of the Conversation Loop: StreamingToolExecutor and 7 Continue Paths&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Claude Code Harness Anatomy #2 — The Heart of the Conversation Loop: StreamingToolExecutor and 7 Continue Paths</title><link>https://ice-ice-bear.github.io/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/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 Harness Anatomy #2 — The Heart of the Conversation Loop: StreamingToolExecutor and 7 Continue Paths" /&gt;&lt;h2 id="overview"&gt;Overview
&lt;/h2&gt;&lt;p&gt;In the first post of this series, we traced the journey of a single &amp;ldquo;hello&amp;rdquo; through 11 files. This post fully dissects the heart of that journey: the &lt;code&gt;while(true)&lt;/code&gt; loop in &lt;code&gt;query.ts&lt;/code&gt;&amp;rsquo;s 1,729 lines. We analyze the resilient execution model created by 7 &lt;code&gt;continue&lt;/code&gt; paths, the 4-stage state machine of &lt;code&gt;StreamingToolExecutor&lt;/code&gt;, and the 3-tier concurrency model of &lt;code&gt;partitionToolCalls()&lt;/code&gt;, then compare how we reproduced these patterns in a Rust prototype.&lt;/p&gt;
&lt;h2 id="analysis-target-10-core-files"&gt;Analysis Target: 10 Core Files
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;#&lt;/th&gt;
 &lt;th&gt;Path&lt;/th&gt;
 &lt;th&gt;Lines&lt;/th&gt;
 &lt;th&gt;Role&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;Immutable runtime gate snapshot&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;Testable I/O boundary (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;Token budget management, auto-continue/stop decisions&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 hooks&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;Core&lt;/strong&gt; &amp;ndash; while(true) turn loop&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;Session wrapper, SDK interface&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;Tool partitioning + concurrency control&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 mid-stream tool pipelining&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;Tool dispatch, permission checks&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 hook pipeline&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;We dissect a total of &lt;strong&gt;6,789 lines&lt;/strong&gt; of core orchestration code.&lt;/p&gt;
&lt;h2 id="1-queryloops-7-continue-paths"&gt;1. queryLoop()&amp;rsquo;s 7 Continue Paths
&lt;/h2&gt;&lt;p&gt;The &lt;code&gt;queryLoop()&lt;/code&gt; function in &lt;code&gt;query.ts&lt;/code&gt; (query.ts:241) is not a simple API call loop. It&amp;rsquo;s a &lt;strong&gt;resilient executor&lt;/strong&gt; with 7 distinct &lt;code&gt;continue&lt;/code&gt; reasons, each handling a unique failure scenario:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Reason&lt;/th&gt;
 &lt;th&gt;Line&lt;/th&gt;
 &lt;th&gt;Description&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;Retry after context collapse drain&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;Retry after reactive compaction (413 recovery)&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;Token escalation from 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;Inject &amp;ldquo;continue writing&amp;rdquo; nudge message&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 hook returned a blocking error&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;Continue due to remaining token budget&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;Next turn after tool execution completes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;The State type is key&lt;/strong&gt; (query.ts:204-217). Loop state is managed as a record with 10 fields. Why a record instead of individual variables? There are 7 &lt;code&gt;continue&lt;/code&gt; sites, each updating via &lt;code&gt;state = { ... }&lt;/code&gt; all at once. Individually assigning 9 variables makes it easy to miss one. &lt;strong&gt;Record updates let the type system catch omissions.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="full-flow-of-a-single-loop-iteration"&gt;Full Flow of a Single Loop Iteration
&lt;/h3&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="mf"&gt;1.&lt;/span&gt; &lt;span class="n"&gt;Preprocessing&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;365&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;447&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;snip&lt;/span&gt; &lt;span class="n"&gt;compaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;micro&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="n"&gt;collapse&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;Auto&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;compaction&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;454&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;543&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;replace&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="k"&gt;continue&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;Blocking&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;628&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;648&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;immediate&lt;/span&gt; &lt;span class="n"&gt;termination&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="n"&gt;exceeded&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="mf"&gt;4.&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="n"&gt;streaming&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;654&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;863&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;consume&lt;/span&gt; &lt;span class="n"&gt;SSE&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;await&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="mf"&gt;5.&lt;/span&gt; &lt;span class="n"&gt;No&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;tool&lt;/span&gt; &lt;span class="n"&gt;exit&lt;/span&gt; &lt;span class="n"&gt;paths&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1062&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1357&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="mi"&gt;413&lt;/span&gt; &lt;span class="n"&gt;recovery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_output&lt;/span&gt; &lt;span class="n"&gt;recovery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt; &lt;span class="n"&gt;hooks&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="mf"&gt;6.&lt;/span&gt; &lt;span class="n"&gt;Tool&lt;/span&gt; &lt;span class="n"&gt;continuation&lt;/span&gt; &lt;span class="n"&gt;paths&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1360&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1728&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;next_turn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="2-streamingtoolexecutors-4-stage-state-machine"&gt;2. StreamingToolExecutor&amp;rsquo;s 4-Stage State Machine
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;StreamingToolExecutor.ts&lt;/code&gt; (530 lines) is the most sophisticated concurrency pattern in Claude Code. The core idea: &lt;strong&gt;start executing completed tool calls while the API response is still streaming&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;When the model calls &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; at once, without pipelining, execution only begins after all three tool blocks have arrived. With pipelining, file reading starts the instant the &lt;code&gt;ReadFile(&amp;quot;a.ts&amp;quot;)&lt;/code&gt; block completes.&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: Pre-canceled&amp;lt;br/&amp;gt;getAbortReason() != null

 executing --&gt; completed: Tool execution finished&amp;lt;br/&amp;gt;or sibling abort

 completed --&gt; yielded: getCompletedResults()&amp;lt;br/&amp;gt;yield in order

 yielded --&gt; [*]

 note right of queued
 processQueue() auto-triggers
 on addTool() and prior
 tool completion
 end note

 note right of completed
 On Bash error:
 siblingAbortController.abort()
 cancels sibling tools only
 end note&lt;/pre&gt;&lt;h3 id="concurrency-decision-logic-canexecutetool-line-129"&gt;Concurrency Decision Logic (canExecuteTool, line 129)
&lt;/h3&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;Execution&lt;/span&gt; &lt;span class="n"&gt;conditions&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;-&lt;/span&gt; &lt;span class="n"&gt;No&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="n"&gt;currently&lt;/span&gt; &lt;span class="n"&gt;executing&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;executingTools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&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;-&lt;/span&gt; &lt;span class="n"&gt;Or&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="k"&gt;tool&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;concurrencySafe&lt;/span&gt; &lt;span class="n"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;executing&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="n"&gt;also&lt;/span&gt; &lt;span class="n"&gt;concurrencySafe&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Read-only tools can execute in parallel, but if even one write tool is present, the next tool waits until it finishes.&lt;/p&gt;
&lt;h3 id="siblingabortcontroller--hierarchical-cancellation"&gt;siblingAbortController &amp;ndash; Hierarchical Cancellation
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;siblingAbortController&lt;/code&gt; (line 46-61) is a child of &lt;code&gt;toolUseContext.abortController&lt;/code&gt;. When a Bash tool throws an error, it calls &lt;code&gt;siblingAbortController.abort('sibling_error')&lt;/code&gt; to &lt;strong&gt;cancel only sibling tools&lt;/strong&gt;. The parent controller is unaffected, so the overall query continues.&lt;/p&gt;
&lt;p&gt;Why do only Bash errors cancel siblings? In &lt;code&gt;mkdir -p dir &amp;amp;&amp;amp; cd dir &amp;amp;&amp;amp; make&lt;/code&gt;, if mkdir fails, subsequent commands are pointless. ReadFile or WebFetch failures are independent and shouldn&amp;rsquo;t affect other tools.&lt;/p&gt;
&lt;h2 id="3-partitiontoolcalls--3-tier-concurrency-model"&gt;3. partitionToolCalls &amp;ndash; 3-Tier Concurrency Model
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;toolOrchestration.ts&lt;/code&gt; (188 lines) defines the entire concurrency model for tool execution.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 TC["Tool call array&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;max 10 concurrent"]
 SEQ["Sequential execution"]
 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;The rule is simple: consecutive &lt;code&gt;isConcurrencySafe&lt;/code&gt; tools are grouped into a single batch, while non-safe tools each become independent batches. This decision comes &lt;strong&gt;from the tool definition itself&lt;/strong&gt; — determined by calling &lt;code&gt;tool.isConcurrencySafe(parsedInput)&lt;/code&gt;. The same tool may have different concurrency safety depending on its input.&lt;/p&gt;
&lt;h3 id="context-modifiers-and-race-conditions"&gt;Context Modifiers and Race Conditions
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Why apply them in order after the batch completes?&lt;/strong&gt; Applying context modifiers immediately during parallel execution creates race conditions. If A completes first and modifies the context, B (still executing) started with the pre-modification context but would see the post-modification state. Applying them in original tool order after batch completion guarantees deterministic results (toolOrchestration.ts:54-62).&lt;/p&gt;
&lt;h2 id="4-tool-execution-pipeline-and-hooks"&gt;4. Tool Execution Pipeline and Hooks
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;runToolUse()&lt;/code&gt; in &lt;code&gt;toolExecution.ts&lt;/code&gt; (1,745 lines, line 337) manages the complete lifecycle of each individual tool call:&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="n"&gt;entry&lt;/span&gt; &lt;span class="n"&gt;point&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;retry&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;deprecated&lt;/span&gt; &lt;span class="n"&gt;aliases&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="n"&gt;check&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;already&lt;/span&gt; &lt;span class="n"&gt;canceled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;return&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="n"&gt;permissions&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;execution&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;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="n"&gt;schema&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="n"&gt;validation&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="n"&gt;custom&lt;/span&gt; &lt;span class="n"&gt;validation&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="n"&gt;Speculative&lt;/span&gt; &lt;span class="n"&gt;classifier&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bash&lt;/span&gt; &lt;span class="n"&gt;only&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="n"&gt;actual&lt;/span&gt; &lt;span class="n"&gt;execution&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="n"&gt;result&lt;/span&gt; &lt;span class="n"&gt;transformation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="the-core-invariant-of-resolvehookpermissiondecision"&gt;The Core Invariant of resolveHookPermissionDecision
&lt;/h3&gt;&lt;p&gt;In &lt;code&gt;resolveHookPermissionDecision()&lt;/code&gt; (toolHooks.ts:332), &lt;strong&gt;a hook&amp;rsquo;s &lt;code&gt;allow&lt;/code&gt; does not bypass settings.json deny/ask rules&lt;/strong&gt; (toolHooks.ts:373). Even if a hook allows, it must still pass &lt;code&gt;checkRuleBasedPermissions()&lt;/code&gt;. This reflects the design principle that &amp;ldquo;hooks are automation helpers, not security bypasses.&amp;rdquo;&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;When hook result is allow:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; Call checkRuleBasedPermissions()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; null means pass (no rules)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; deny means rule overrides hook
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; ask means user prompt required
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="5-rust-comparison--152-lines-vs-1729-lines"&gt;5. Rust Comparison &amp;ndash; 152 Lines vs 1,729 Lines
&lt;/h2&gt;&lt;p&gt;Rust&amp;rsquo;s &lt;code&gt;ConversationRuntime::run_turn()&lt;/code&gt; consists of &lt;strong&gt;152 lines in a single &lt;code&gt;loop {}&lt;/code&gt;&lt;/strong&gt; (conversation.rs:183-272). Of the 7 TS continue paths, only &lt;code&gt;next_turn&lt;/code&gt; (next turn after tool execution) exists in Rust.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;TS Continue Reason&lt;/th&gt;
 &lt;th&gt;Rust Status&lt;/th&gt;
 &lt;th&gt;Why&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;Not implemented&lt;/td&gt;
 &lt;td&gt;No context collapse&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;Not implemented&lt;/td&gt;
 &lt;td&gt;No 413 recovery&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;Not implemented&lt;/td&gt;
 &lt;td&gt;No 8k-&amp;gt;64k escalation&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;Not implemented&lt;/td&gt;
 &lt;td&gt;No multi-turn nudge&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;Not implemented&lt;/td&gt;
 &lt;td&gt;No stop hooks&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;Not implemented&lt;/td&gt;
 &lt;td&gt;No token budget system&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;Implemented&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Re-calls API after tool results&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="the-most-critical-gap-synchronous-api-consumption"&gt;The Most Critical Gap: Synchronous API Consumption
&lt;/h3&gt;&lt;p&gt;The Rust &lt;code&gt;ApiClient&lt;/code&gt; trait signature says it all:&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;The return type is &lt;code&gt;Vec&amp;lt;AssistantEvent&amp;gt;&lt;/code&gt;. &lt;strong&gt;It&amp;rsquo;s not streaming.&lt;/strong&gt; It collects all SSE events and returns them as a vector. This means when the model calls 5 ReadFiles, TS can finish executing the first ReadFile while still streaming, but Rust must wait for all 5 to finish streaming before starting sequential execution. &lt;strong&gt;The latency gap grows proportionally with the number of tools.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="6-rust-prototype--bridging-the-gap"&gt;6. Rust Prototype &amp;ndash; Bridging the Gap
&lt;/h2&gt;&lt;p&gt;In the S04 prototype, we implemented an orchestration layer that bridges 3 P0 gaps:&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 subgraph TS["TS Streaming Pipeline"]
 direction TB
 ts1["SSE event stream"]
 ts2["StreamingToolExecutor&amp;lt;br/&amp;gt;4-state machine"]
 ts3["getCompletedResults()&amp;lt;br/&amp;gt;guaranteed yield order"]
 ts1 --&gt; ts2 --&gt; ts3
 end

 subgraph Rust["Rust Prototype"]
 direction TB
 rs1["EventStream&amp;lt;br/&amp;gt;tokio async"]
 rs2["StreamingPipeline&amp;lt;br/&amp;gt;tokio::spawn + mpsc"]
 rs3["Post-MessageEnd&amp;lt;br/&amp;gt;channel collect + sort"]
 rs1 --&gt; rs2 --&gt; rs3
 end

 subgraph Bridge["Core Mappings"]
 direction TB
 b1["yield -&gt; tx.send()"]
 b2["yield* -&gt; channel forwarding"]
 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-key-implementations-in-the-prototype"&gt;3 Key Implementations in the Prototype
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;1. Async streaming&lt;/strong&gt;: Extended the &lt;code&gt;ApiClient&lt;/code&gt; trait to an async stream. Since &lt;code&gt;MessageStream::next_event()&lt;/code&gt; is already async, only the consumer side needed changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Tool pipelining&lt;/strong&gt;: On receiving a &lt;code&gt;ToolUseEnd&lt;/code&gt; event, assembles a &lt;code&gt;ToolCall&lt;/code&gt; from accumulated input and immediately starts background execution via &lt;code&gt;tokio::spawn&lt;/code&gt;. Collects results in completion order via &lt;code&gt;mpsc::unbounded_channel&lt;/code&gt;, then sorts back to original order.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 3-tier concurrency&lt;/strong&gt;: Partitions by &lt;code&gt;ToolCategory&lt;/code&gt; enum (ReadOnly/Write/BashLike). ReadOnly batches use &lt;code&gt;Semaphore(10)&lt;/code&gt; + &lt;code&gt;tokio::spawn&lt;/code&gt; for up to 10 parallel tasks. BashLike runs sequentially with remaining tasks aborted on error.&lt;/p&gt;
&lt;h3 id="prototype-coverage"&gt;Prototype Coverage
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;TS Feature&lt;/th&gt;
 &lt;th&gt;Prototype&lt;/th&gt;
 &lt;th&gt;Status&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;Implemented&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;Implemented&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;&lt;code&gt;break&lt;/code&gt; on BashLike error&lt;/td&gt;
 &lt;td&gt;Simplified&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;tokio::spawn&lt;/code&gt; on &lt;code&gt;ToolUseEnd&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Implemented&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;Implemented&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;Implemented&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;Incomplete&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;413 recovery / max_output escalation&lt;/td&gt;
 &lt;td&gt;&amp;ndash;&lt;/td&gt;
 &lt;td&gt;Not implemented&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;Not implemented&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="stop-condition-comparison"&gt;Stop Condition Comparison
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Condition&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;No tools (end_turn)&lt;/td&gt;
 &lt;td&gt;Execute &lt;code&gt;handleStopHooks()&lt;/code&gt; then exit&lt;/td&gt;
 &lt;td&gt;Immediate &lt;code&gt;break&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Token budget exceeded&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;checkTokenBudget()&lt;/code&gt; with 3 decisions&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;max_output_tokens&lt;/td&gt;
 &lt;td&gt;Escalation + multi-turn recovery&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;413 prompt-too-long&lt;/td&gt;
 &lt;td&gt;Context collapse + reactive compaction&lt;/td&gt;
 &lt;td&gt;Error propagation&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; parameter (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;Diminishing returns&lt;/td&gt;
 &lt;td&gt;3+ turns with &amp;lt;500 token increase&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;checkTokenBudget()&lt;/code&gt; in &lt;code&gt;tokenBudget.ts&lt;/code&gt; (93 lines) controls &lt;strong&gt;whether to continue responding, not prompt size&lt;/strong&gt;. &lt;code&gt;COMPLETION_THRESHOLD = 0.9&lt;/code&gt; (continue if below 90% of total budget), &lt;code&gt;DIMINISHING_THRESHOLD = 500&lt;/code&gt; (stop if 3+ consecutive turns each produce fewer than 500 tokens, indicating diminishing returns). The &lt;code&gt;nudgeMessage&lt;/code&gt; explicitly instructs &amp;ldquo;do not summarize.&amp;rdquo;&lt;/p&gt;
&lt;h2 id="the-core-design-decision--why-asyncgenerator"&gt;The Core Design Decision &amp;ndash; Why AsyncGenerator
&lt;/h2&gt;&lt;p&gt;The entire pipeline is an &lt;code&gt;async function*&lt;/code&gt; chain:&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;The key benefit of this choice: &lt;strong&gt;implementing complex state machines without inversion of control&lt;/strong&gt;. At each of the 7 &lt;code&gt;continue&lt;/code&gt; paths, you construct state explicitly with &lt;code&gt;state = { ... }&lt;/code&gt; and &lt;code&gt;continue&lt;/code&gt;. With a callback-based approach, state management would be scattered, making it difficult to guarantee consistency across 7 recovery paths.&lt;/p&gt;
&lt;p&gt;In Rust, since the &lt;code&gt;yield&lt;/code&gt; keyword isn&amp;rsquo;t stabilized, &lt;code&gt;tokio::sync::mpsc&lt;/code&gt; channels serve as the replacement. &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; channel forwarding, &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="insights"&gt;Insights
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;query.ts&amp;rsquo;s 7 continue paths are not &amp;ldquo;error handling&amp;rdquo; but a &amp;ldquo;resilience engine&amp;rdquo;&lt;/strong&gt; &amp;ndash; It collapses context on 413 errors, escalates tokens on max_output, and feeds back errors to the model on stop hook blocking. This recovery pipeline ensures stability during long-running autonomous tasks. Reproducing this in Rust requires state management beyond a simple &lt;code&gt;loop {}&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;StreamingToolExecutor is a UX decision, not a performance optimization&lt;/strong&gt; &amp;ndash; Executing 5 tools sequentially makes users wait for the sum of all execution times. Pipelining reduces not benchmark numbers but the perceived &amp;ldquo;waiting for a response&amp;rdquo; time. In the Rust prototype, we implemented this in under 20 lines using &lt;code&gt;tokio::spawn&lt;/code&gt; + &lt;code&gt;mpsc&lt;/code&gt; channels.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The dual structure of static partitioning + runtime concurrency balances safety and performance&lt;/strong&gt; &amp;ndash; &lt;code&gt;partitionToolCalls()&lt;/code&gt; divides batches at build time, while &lt;code&gt;canExecuteTool()&lt;/code&gt; judges executability at runtime. Thanks to this dual structure, the non-streaming path (&lt;code&gt;runTools&lt;/code&gt;) and the streaming path (&lt;code&gt;StreamingToolExecutor&lt;/code&gt;) share identical concurrency semantics.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Next post: &lt;a class="link" href="https://ice-ice-bear.github.io/posts/2026-04-06-harness-anatomy-3/" &gt;#3 &amp;ndash; The Design Philosophy of 42 Tools, from BashTool to AgentTool&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Claude Code Harness Anatomy #3 — The Design Philosophy of 42 Tools, from BashTool to AgentTool</title><link>https://ice-ice-bear.github.io/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/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 Harness Anatomy #3 — The Design Philosophy of 42 Tools, from BashTool to AgentTool" /&gt;&lt;h2 id="overview"&gt;Overview
&lt;/h2&gt;&lt;p&gt;Claude Code has 42 tools. This post dissects the &amp;ldquo;tools know themselves&amp;rdquo; pattern implemented by the 30+ member &lt;code&gt;Tool.ts&lt;/code&gt; interface, classifies all 42 tools into 8 families, and deep-dives into the most complex ones: BashTool&amp;rsquo;s 6-layer security chain (12,411 lines), AgentTool&amp;rsquo;s 4 spawn modes (6,782 lines), FileEditTool&amp;rsquo;s string matching strategy, MCPTool&amp;rsquo;s empty-shell proxy pattern, and the Task state machine.&lt;/p&gt;
&lt;h2 id="1-tool-interface--tools-know-themselves"&gt;1. Tool Interface &amp;ndash; &amp;ldquo;Tools Know Themselves&amp;rdquo;
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;Tool.ts&lt;/code&gt; (792 lines) is the contract for the tool system. The &lt;code&gt;Tool&lt;/code&gt; type (Tool.ts:362-695) that every tool implements consists of &lt;strong&gt;30+ members&lt;/strong&gt; across four domains:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Domain&lt;/th&gt;
 &lt;th&gt;Key Members&lt;/th&gt;
 &lt;th&gt;Role&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Execution contract&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;Core tool logic&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Metadata&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;Search and display&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Concurrency/Safety&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;Orchestration decisions&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;UI rendering&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;renderToolUseMessage()&lt;/code&gt; + 10 more&lt;/td&gt;
 &lt;td&gt;Terminal display&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Why so many members in one interface?&lt;/strong&gt; When the orchestrator (&lt;code&gt;toolExecution.ts&lt;/code&gt;) calls a tool, it can read all metadata directly from the tool object without any external mapping tables. This is the foundation of a plugin architecture where adding a new tool is &lt;strong&gt;self-contained within a single directory&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="toolusecontext--42-fields-of-execution-environment"&gt;ToolUseContext &amp;ndash; 42 Fields of Execution Environment
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;ToolUseContext&lt;/code&gt; (Tool.ts:158-300) is the environment context injected during tool execution. Spanning 142 lines, it defines 42 fields:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;abortController&lt;/code&gt;: Cancellation propagation for the 3-tier concurrency model&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getAppState()&lt;/code&gt;/&lt;code&gt;setAppState()&lt;/code&gt;: Global state access (permissions, todos, teams)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readFileState&lt;/code&gt;: LRU cache-based change detection&lt;/li&gt;
&lt;li&gt;&lt;code&gt;contentReplacementState&lt;/code&gt;: Save large results to disk, return summaries only&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tools are not isolated functions — they need access to the harness&amp;rsquo;s entire state. FileReadTool uses the cache to detect changes, AgentTool registers sub-agent state, and BashTool can interrupt sibling processes.&lt;/p&gt;
&lt;h3 id="buildtools-fail-closed-defaults"&gt;buildTool()&amp;rsquo;s fail-closed Defaults
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;buildTool()&lt;/code&gt; (Tool.ts:783) takes a &lt;code&gt;ToolDef&lt;/code&gt; and returns a complete &lt;code&gt;Tool&lt;/code&gt; with defaults filled in. The defaults follow a &lt;strong&gt;fail-closed&lt;/strong&gt; principle (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; (assume unsafe)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isReadOnly&lt;/code&gt; -&amp;gt; &lt;code&gt;false&lt;/code&gt; (assume writes)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If a new tool doesn&amp;rsquo;t explicitly declare concurrency/read-only status, it takes the most conservative path (sequential execution, write permission required). &lt;strong&gt;This structurally prevents the bug of accidentally running an unsafe tool in parallel.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="2-42-tools-in-8-families"&gt;2. 42 Tools in 8 Families
&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 lines"]
 W2["FileEditTool / FileWriteTool"]
 W3["AgentTool 6,782 lines"]
 W4["MCPTool / SkillTool"]
 W5["Task 5 / Todo"]
 W6["Config / PlanMode / Worktree"]
 end

 subgraph orch["Orchestrator"]
 O["partitionToolCalls()&amp;lt;br/&amp;gt;toolOrchestration.ts"]
 end

 O --&gt;|"Parallel batch"| safe
 O --&gt;|"Sequential execution"| unsafe

 style safe fill:#e8f5e9
 style unsafe fill:#ffebee&lt;/pre&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Family&lt;/th&gt;
 &lt;th&gt;Count&lt;/th&gt;
 &lt;th&gt;Representative Tool&lt;/th&gt;
 &lt;th&gt;Key Characteristic&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Filesystem&lt;/td&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;td&gt;FileReadTool (1,602 lines)&lt;/td&gt;
 &lt;td&gt;PDF/image/notebook support, token limits&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Execution&lt;/td&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;BashTool (12,411 lines)&lt;/td&gt;
 &lt;td&gt;6-layer security, command semantics&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Agent/Team&lt;/td&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;AgentTool (6,782 lines)&lt;/td&gt;
 &lt;td&gt;4 spawn modes, recursive harness&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Task management&lt;/td&gt;
 &lt;td&gt;7&lt;/td&gt;
 &lt;td&gt;TaskUpdateTool (484 lines)&lt;/td&gt;
 &lt;td&gt;State machine, verification nudge&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 lines)&lt;/td&gt;
 &lt;td&gt;Empty-shell proxy&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Web/External&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;WebFetchTool (1,131 lines)&lt;/td&gt;
 &lt;td&gt;Parallel safe&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;State/Config&lt;/td&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;td&gt;ConfigTool (809 lines)&lt;/td&gt;
 &lt;td&gt;Session state changes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Infra/Utility&lt;/td&gt;
 &lt;td&gt;11&lt;/td&gt;
 &lt;td&gt;SkillTool (1,477 lines)&lt;/td&gt;
 &lt;td&gt;Command-to-tool bridge&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Only 10 of 42 (24%) are parallel-safe, but these 10 are the most frequently called tools (Read, Glob, Grep, Web), so perceived parallelism is higher than the ratio suggests.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="3-bashtool--6-layer-security-chain"&gt;3. BashTool &amp;ndash; 6-Layer Security Chain
&lt;/h2&gt;&lt;p&gt;BashTool is not a simple shell executor. Because &lt;strong&gt;arbitrary code execution&lt;/strong&gt; is an inherent risk, more than half of its 12,411 lines are security layers.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB
 A["Model: Bash call"] --&gt; B{"validateInput"}
 B --&gt;|"sleep pattern blocked"| B1["Return error"]
 B --&gt;|"Pass"| C{"6-layer security chain"}

 subgraph chain["Security chain"]
 C1["1. bashSecurity.ts&amp;lt;br/&amp;gt;2,592 lines -- command structure analysis"]
 C2["2. bashPermissions.ts&amp;lt;br/&amp;gt;2,621 lines -- rule matching"]
 C3["3. readOnlyValidation.ts&amp;lt;br/&amp;gt;1,990 lines -- read-only determination"]
 C4["4. pathValidation.ts&amp;lt;br/&amp;gt;1,303 lines -- path-based security"]
 C5["5. sedValidation.ts&amp;lt;br/&amp;gt;684 lines -- sed-specific security"]
 C6["6. shouldUseSandbox.ts&amp;lt;br/&amp;gt;153 lines -- sandbox decision"]
 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["Request user approval"]
 D --&gt;|"deny"| G["Denied"]
 E --&gt; H["Result processing&amp;lt;br/&amp;gt;interpretCommandResult()&amp;lt;br/&amp;gt;trackGitOperations()"]

 style chain fill:#fff3e0&lt;/pre&gt;&lt;p&gt;Each layer handles a different threat:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;bashSecurity.ts&lt;/strong&gt; (2,592 lines): Blocks command substitution (&lt;code&gt;$()&lt;/code&gt;, &lt;code&gt;`&lt;/code&gt;), Zsh module-based attacks. Key: &lt;strong&gt;only metacharacters in unquoted contexts are classified as dangerous&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bashPermissions.ts&lt;/strong&gt; (2,621 lines): Rule-based allow/deny/ask. &lt;code&gt;stripAllLeadingEnvVars()&lt;/code&gt; + &lt;code&gt;stripSafeWrappers()&lt;/code&gt; strip wrappers to extract the actual command&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;readOnlyValidation.ts&lt;/strong&gt; (1,990 lines): If read-only, then &lt;code&gt;isConcurrencySafe: true&lt;/code&gt; — parallel execution allowed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pathValidation.ts&lt;/strong&gt; (1,303 lines): Per-command path extraction rules for path safety judgment&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sedValidation.ts&lt;/strong&gt; (684 lines): sed&amp;rsquo;s &lt;code&gt;w&lt;/code&gt; and &lt;code&gt;e&lt;/code&gt; flags can write files/execute arbitrary code — blocked separately&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shouldUseSandbox.ts&lt;/strong&gt; (153 lines): Final isolation decision&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Command semantics&lt;/strong&gt; (&lt;code&gt;commandSemantics.ts&lt;/code&gt;): &lt;code&gt;grep&lt;/code&gt; and &lt;code&gt;diff&lt;/code&gt; return exit code 1 as a normal result, not an error. The &lt;code&gt;COMMAND_SEMANTICS&lt;/code&gt; Map defines per-command interpretation rules.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rust porting implications&lt;/strong&gt;: Either reproduce all 6 layers wholesale, or simplify to sandbox-only. Skipping intermediate layers creates security holes.&lt;/p&gt;
&lt;h2 id="4-agenttool--4-spawn-modes"&gt;4. AgentTool &amp;ndash; 4 Spawn Modes
&lt;/h2&gt;&lt;p&gt;AgentTool is less of a &amp;ldquo;tool&amp;rdquo; and more of an &lt;strong&gt;agent orchestrator&lt;/strong&gt;. The key: &lt;code&gt;runAgent()&lt;/code&gt; recursively calls the harness&amp;rsquo;s &lt;code&gt;query()&lt;/code&gt; loop. Child agents receive the same tools, API access, and security checks as the parent.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Mode&lt;/th&gt;
 &lt;th&gt;Trigger&lt;/th&gt;
 &lt;th&gt;Context Sharing&lt;/th&gt;
 &lt;th&gt;Background&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Synchronous&lt;/td&gt;
 &lt;td&gt;Default&lt;/td&gt;
 &lt;td&gt;None (prompt only)&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Async&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;run_in_background: true&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Fork&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;subagent_type&lt;/code&gt; omitted&lt;/td&gt;
 &lt;td&gt;Full parent context&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Remote&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;isolation: &amp;quot;remote&amp;quot;&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="fork-sub-agents--byte-identical-prefix"&gt;Fork Sub-agents &amp;ndash; Byte-Identical Prefix
&lt;/h3&gt;&lt;p&gt;Forks &lt;strong&gt;inherit the parent&amp;rsquo;s full conversation context&lt;/strong&gt;. To share prompt cache, all fork children are designed to produce byte-identical API request prefixes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tool use results replaced with placeholders&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FORK_BOILERPLATE_TAG&lt;/code&gt; prevents recursive forking&lt;/li&gt;
&lt;li&gt;Model kept identical (&lt;code&gt;model: 'inherit'&lt;/code&gt;) — different models cause cache misses&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="memory-system-agentmemoryts"&gt;Memory System (agentMemory.ts)
&lt;/h3&gt;&lt;p&gt;Per-agent persistent memory is managed across 3 scopes:&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; — user-global&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; — project-shared (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; — local-only&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="5-fileedittool--partial-replacement-pattern"&gt;5. FileEditTool &amp;ndash; Partial Replacement Pattern
&lt;/h2&gt;&lt;p&gt;FileEditTool (1,812 lines) performs &lt;strong&gt;&lt;code&gt;old_string&lt;/code&gt; -&amp;gt; &lt;code&gt;new_string&lt;/code&gt; patches&lt;/strong&gt; rather than full file writes. The model doesn&amp;rsquo;t need to output the entire file, saving tokens and enabling diff-based review.&lt;/p&gt;
&lt;p&gt;Matching strategy:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Exact string matching&lt;/strong&gt;: &lt;code&gt;fileContent.includes(searchString)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quote normalization&lt;/strong&gt;: Convert curly quotes -&amp;gt; straight quotes and retry, with &lt;code&gt;preserveQuoteStyle()&lt;/code&gt; preserving the original style&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uniqueness validation&lt;/strong&gt;: Fails if &lt;code&gt;old_string&lt;/code&gt; is not unique in the file (unless &lt;code&gt;replace_all&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Concurrency protection&lt;/strong&gt;: The &lt;code&gt;readFileState&lt;/code&gt; Map stores per-file last-read timestamps. During editing, it compares against the on-disk modification time to detect external changes. This is why the &amp;ldquo;Read before Edit&amp;rdquo; rule is enforced in the prompt.&lt;/p&gt;
&lt;h2 id="6-mcptool--empty-shell-proxy"&gt;6. MCPTool &amp;ndash; Empty-Shell Proxy
&lt;/h2&gt;&lt;p&gt;MCPTool (1,086 lines) is where &lt;strong&gt;a single tool definition represents hundreds of external tools&lt;/strong&gt;. At build time it&amp;rsquo;s an empty shell; at runtime, &lt;code&gt;mcpClient.ts&lt;/code&gt; clones and overrides it per server:&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 -- core methods have &amp;#34;Overridden in mcpClient.ts&amp;#34; comments
&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;// replaced at runtime with &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;// replaced at runtime with actual MCP call
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The UI collapse classification (&lt;code&gt;classifyForCollapse.ts&lt;/code&gt;, 604 lines) uses 139 SEARCH_TOOLS and 280+ READ_TOOLS names to determine whether an MCP tool is a read/search operation. Unknown tools are not collapsed (conservative approach).&lt;/p&gt;
&lt;h2 id="7-task-state-machine--agent-ipc"&gt;7. Task State Machine &amp;ndash; Agent IPC
&lt;/h2&gt;&lt;p&gt;TaskUpdateTool (406 lines) state flow: &lt;code&gt;pending -&amp;gt; in_progress -&amp;gt; completed&lt;/code&gt; or &lt;code&gt;deleted&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Key behaviors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Auto-assign owner&lt;/strong&gt;: Current agent name is automatically assigned on &lt;code&gt;in_progress&lt;/code&gt; transition&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verification nudge&lt;/strong&gt;: After 3+ tasks completed without a verification step, recommends spawning a verification agent&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Message routing&lt;/strong&gt; (SendMessageTool 917 lines): By name, &lt;code&gt;*&lt;/code&gt; broadcast, &lt;code&gt;uds:path&lt;/code&gt; Unix domain socket, &lt;code&gt;bridge:session&lt;/code&gt; remote peer, agent ID resume&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Task/SendMessage are not simple utilities but the &lt;strong&gt;inter-process communication (IPC)&lt;/strong&gt; foundation of the multi-agent system.&lt;/p&gt;
&lt;h2 id="ts-vs-rust-comparison"&gt;TS vs Rust Comparison
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Aspect&lt;/th&gt;
 &lt;th&gt;TS (42 tools)&lt;/th&gt;
 &lt;th&gt;Rust (10 tools)&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Tool definition&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;Tool&lt;/code&gt; interface + &lt;code&gt;buildTool()&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;ToolSpec&lt;/code&gt; struct + &lt;code&gt;mvp_tool_specs()&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Input schema&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; direct JSON Schema&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Concurrency declaration&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;isConcurrencySafe(parsedInput)&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;None — sequential execution&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Permission check&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 rendering&lt;/td&gt;
 &lt;td&gt;10+ render methods (React/Ink)&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;MCP integration&lt;/td&gt;
 &lt;td&gt;MCPTool + &lt;code&gt;inputJSONSchema&lt;/code&gt; dual path&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Size comparison&lt;/td&gt;
 &lt;td&gt;~48,000 lines (tool code only)&lt;/td&gt;
 &lt;td&gt;~1,300 lines (single lib.rs)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Key gap&lt;/strong&gt;: The Rust port only implements the execution contract (&lt;code&gt;call&lt;/code&gt; equivalent); concurrency declarations, permission pipeline, UI rendering, and lazy-loading optimizations are all missing.&lt;/p&gt;
&lt;h2 id="insights"&gt;Insights
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security is a chain, not a single checkpoint&lt;/strong&gt; &amp;ndash; BashTool&amp;rsquo;s 6 layers each handle different threats. bashSecurity handles command structure, bashPermissions handles rule matching, pathValidation handles path safety. If any link in this chain is missing, an attack surface opens. Combined with the fail-closed principle, the conservative strategy of &amp;ldquo;block when uncertain&amp;rdquo; permeates the entire system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Agents are recursive harness instances&lt;/strong&gt; &amp;ndash; The fact that AgentTool&amp;rsquo;s &lt;code&gt;runAgent()&lt;/code&gt; recursively calls the harness&amp;rsquo;s &lt;code&gt;query()&lt;/code&gt; loop means &amp;ldquo;agent&amp;rdquo; is not a separate system but &lt;strong&gt;a different configuration of the same harness&lt;/strong&gt;. It swaps only the tool pool while reusing the same security, hooks, and orchestration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Only 10 of 42 tools are concurrency-safe, yet perceived parallelism is high&lt;/strong&gt; &amp;ndash; The 10 tools representing only 24% of the total (Read, Glob, Grep, Web, LSP) happen to be the most frequently called. This asymmetry demonstrates the practical value of the 3-tier concurrency model. &lt;code&gt;buildTool()&lt;/code&gt;&amp;rsquo;s fail-closed default (&lt;code&gt;isConcurrencySafe: false&lt;/code&gt;) forms the safety boundary, structurally preventing new tool developers from incorrectly declaring concurrency safety.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Next post: &lt;a class="link" href="https://ice-ice-bear.github.io/posts/2026-04-06-harness-anatomy-4/" &gt;#4 &amp;ndash; Runtime Hooks: 26+ Events and CLAUDE.md 6-Stage Discovery&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Claude Code Harness Anatomy #4 — Runtime Hooks: 26+ Events and CLAUDE.md 6-Stage Discovery</title><link>https://ice-ice-bear.github.io/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/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 Harness Anatomy #4 — Runtime Hooks: 26+ Events and CLAUDE.md 6-Stage Discovery" /&gt;&lt;h2 id="overview"&gt;Overview
&lt;/h2&gt;&lt;p&gt;In Claude Code, the word &amp;ldquo;hook&amp;rdquo; refers to &lt;strong&gt;two completely different systems&lt;/strong&gt;. Runtime hooks (&lt;code&gt;toolHooks.ts&lt;/code&gt; + &lt;code&gt;utils/hooks.ts&lt;/code&gt;) are a security/extension pipeline that executes shell scripts before and after tool execution, while React hooks (&lt;code&gt;hooks/*.ts&lt;/code&gt;, 85+) are state management code for the terminal UI. Missing this distinction leads to a 85x overestimation of the Rust reimplementation scope. This post analyzes the PreToolUse/PostToolUse pipeline of runtime hooks, the security invariant of &lt;code&gt;resolveHookPermissionDecision()&lt;/code&gt;, the 9-category classification of 85 React hooks, and CLAUDE.md&amp;rsquo;s 6-stage discovery with token budget management.&lt;/p&gt;
&lt;h2 id="1-runtime-hooks-vs-react-hooks--the-key-distinction"&gt;1. Runtime Hooks vs React Hooks &amp;ndash; The Key Distinction
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Dimension&lt;/th&gt;
 &lt;th&gt;Runtime Hooks (toolHooks.ts + utils/hooks.ts)&lt;/th&gt;
 &lt;th&gt;React Hooks (hooks/*.ts)&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Executor&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 render cycle&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Configuration&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;settings.json &lt;code&gt;hooks&lt;/code&gt; field, shell commands&lt;/td&gt;
 &lt;td&gt;Source code &lt;code&gt;import&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Execution timing&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Before/after tool use, session start, etc. (26+ events)&lt;/td&gt;
 &lt;td&gt;Component mount/update&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;User-defined&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Yes — users register shell scripts&lt;/td&gt;
 &lt;td&gt;No — internal code&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Result format&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;JSON stdout (allow/deny/ask/rewrite)&lt;/td&gt;
 &lt;td&gt;React state changes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Rust reimplementation&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Required — core of tool execution pipeline&lt;/td&gt;
 &lt;td&gt;Not needed — TUI only&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="2-pretooluse-pipeline--7-yield-variants"&gt;2. PreToolUse Pipeline &amp;ndash; 7 Yield Variants
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;runPreToolUseHooks()&lt;/code&gt; (toolHooks.ts:435-650) is designed as an AsyncGenerator. Called before tool execution, it emits the following yield types:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;message&lt;/code&gt;&lt;/strong&gt;: Progress messages (hook start/error/cancel)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;hookPermissionResult&lt;/code&gt;&lt;/strong&gt;: allow/deny/ask decision&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;hookUpdatedInput&lt;/code&gt;&lt;/strong&gt;: Input rewrite (changes input without a permission decision)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;preventContinuation&lt;/code&gt;&lt;/strong&gt;: Execution halt flag&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;stopReason&lt;/code&gt;&lt;/strong&gt;: Halt reason string&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;additionalContext&lt;/code&gt;&lt;/strong&gt;: Additional context to pass to the model&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;stop&lt;/code&gt;&lt;/strong&gt;: Immediate halt&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Why AsyncGenerator?&lt;/strong&gt; Hooks execute sequentially, and each hook&amp;rsquo;s result affects subsequent processing. Promise chaining returns only the final result, and event emitters lack type safety. AsyncGenerator is the only pattern that lets the caller consume each result and halt mid-stream.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 subgraph "PreToolUse Pipeline"
 A["toolExecution.ts&amp;lt;br/&amp;gt;Tool call begins"]
 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;event + pattern matching"]
 E["spawn() shell command&amp;lt;br/&amp;gt;stdin: JSON, stdout: result"]
 F["HookResult parsing&amp;lt;br/&amp;gt;allow / deny / ask / rewrite"]
 end

 subgraph "Permission Resolution"
 G["resolveHookPermission&amp;lt;br/&amp;gt;Decision()&amp;lt;br/&amp;gt;toolHooks.ts:332"]
 H{"Hook result?"}
 I["allow: checkRule&amp;lt;br/&amp;gt;BasedPermissions()&amp;lt;br/&amp;gt;rules override hooks"]
 J["deny: immediate rejection"]
 K["ask: canUseTool()&amp;lt;br/&amp;gt;user prompt"]
 end

 subgraph "Tool Execution"
 L["tool.call()"]
 end

 subgraph "PostToolUse"
 M["runPostToolUseHooks()&amp;lt;br/&amp;gt;result transform / block"]
 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;|"Rules pass"| 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;The core invariant of &lt;code&gt;resolveHookPermissionDecision()&lt;/code&gt; (toolHooks.ts:332-433): &lt;strong&gt;a hook&amp;rsquo;s &lt;code&gt;allow&lt;/code&gt; does not bypass settings.json deny/ask rules&lt;/strong&gt; (toolHooks.ts:325-327).&lt;/p&gt;
&lt;p&gt;The processing logic has 3 stages:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stage 1 &amp;ndash; allow handling&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; Call checkRuleBasedPermissions()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; null -&amp;gt; no rules, hook allow passes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; deny -&amp;gt; rule overrides hook (security first!)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; ask -&amp;gt; user prompt required
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Why doesn&amp;rsquo;t allow bypass?&lt;/strong&gt; This is a deliberate security decision. If an external shell script returning &lt;code&gt;{&amp;quot;decision&amp;quot;:&amp;quot;allow&amp;quot;}&lt;/code&gt; could override &lt;code&gt;settings.json&lt;/code&gt; deny rules, a malicious hook could circumvent security policies. &lt;strong&gt;Rules always take precedence over hooks.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stage 2 &amp;ndash; deny&lt;/strong&gt; (toolHooks.ts:408-411): Immediate rejection, no further checks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stage 3 &amp;ndash; ask/none&lt;/strong&gt; (toolHooks.ts:413-432): Calls &lt;code&gt;canUseTool()&lt;/code&gt; for user prompt.&lt;/p&gt;
&lt;h3 id="26-event-types"&gt;26+ Event Types
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;getMatchingHooks()&lt;/code&gt; (utils/hooks.ts:1603-1682) handles hook matching:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tool events&lt;/strong&gt;: PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Session events&lt;/strong&gt;: SessionStart, SessionEnd, Setup&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent events&lt;/strong&gt;: SubagentStart, SubagentStop, TeammateIdle&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Task events&lt;/strong&gt;: TaskCreated, TaskCompleted&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System events&lt;/strong&gt;: Notification, ConfigChange, FileChanged, InstructionsLoaded&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compact events&lt;/strong&gt;: PreCompact, PostCompact&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Input events&lt;/strong&gt;: UserPromptSubmit, Elicitation, ElicitationResult&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stop events&lt;/strong&gt;: Stop, StopFailure&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Matched hooks execute &lt;strong&gt;sequentially&lt;/strong&gt;, and if one denies, subsequent hooks are not executed.&lt;/p&gt;
&lt;h2 id="3-85-react-hooks--9-category-classification"&gt;3. 85 React Hooks &amp;ndash; 9 Category Classification
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;mindmap
 root(("TS Hook System"))
 Runtime Hooks
 toolHooks.ts 651 lines
 PreToolUse
 PostToolUse
 PostToolUseFailure
 utils/hooks.ts ~5000 lines
 26+ event types
 Shell spawn
 Async protocol
 React Hooks 85+
 Permission 3
 useCanUseTool
 PermissionContext
 UI Input 11
 useTextInput
 useVimInput
 useTypeahead
 UI Display 11
 useVirtualScroll
 useDiffData
 State/Config 12
 useSettings
 useSessionBackgrounding
 Integration/Remote 12
 useRemoteSession
 useReplBridge
 Features 20
 useVoice
 useSwarm
 useTasks
 Notifications 16
 notifs/ directory
 Tools/Keybindings 5
 useMergedTools
 Additional 5+
 fileSuggestions
 useManagePlugins&lt;/pre&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Category&lt;/th&gt;
 &lt;th&gt;Count&lt;/th&gt;
 &lt;th&gt;Rust Reimpl&lt;/th&gt;
 &lt;th&gt;Representative Hook&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Permission&lt;/td&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;Partial (bridge)&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useCanUseTool&lt;/code&gt; (203 lines)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;UI Input&lt;/td&gt;
 &lt;td&gt;11&lt;/td&gt;
 &lt;td&gt;Not needed&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useTextInput&lt;/code&gt; (529 lines), &lt;code&gt;useVimInput&lt;/code&gt; (316 lines)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;UI Display&lt;/td&gt;
 &lt;td&gt;11&lt;/td&gt;
 &lt;td&gt;Not needed&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useVirtualScroll&lt;/code&gt; (721 lines)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;State/Config&lt;/td&gt;
 &lt;td&gt;12&lt;/td&gt;
 &lt;td&gt;Not needed&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useSessionBackgrounding&lt;/code&gt; (158 lines)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Integration/Remote&lt;/td&gt;
 &lt;td&gt;12&lt;/td&gt;
 &lt;td&gt;Not needed&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useRemoteSession&lt;/code&gt; (605 lines)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Features/Notifications&lt;/td&gt;
 &lt;td&gt;20&lt;/td&gt;
 &lt;td&gt;Not needed&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useVoice&lt;/code&gt; (1,144 lines)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Notifications/Banners&lt;/td&gt;
 &lt;td&gt;16&lt;/td&gt;
 &lt;td&gt;Not needed&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;notifs/&lt;/code&gt; directory&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Tools/Keybindings&lt;/td&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;td&gt;Not needed&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useMergedTools&lt;/code&gt; (44 lines)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Additional&lt;/td&gt;
 &lt;td&gt;5+&lt;/td&gt;
 &lt;td&gt;Not needed&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;fileSuggestions&lt;/code&gt; (811 lines)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Key takeaway&lt;/strong&gt;: What Rust needs to reimplement is only the runtime pipeline of &lt;code&gt;toolHooks.ts&lt;/code&gt; (651 lines) + &lt;code&gt;utils/hooks.ts&lt;/code&gt; (~5,000 lines). The 85 React hooks totaling 15,000+ lines are out of scope.&lt;/p&gt;
&lt;h2 id="4-claudemd-6-stage-discovery"&gt;4. CLAUDE.md 6-Stage Discovery
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;getMemoryFiles()&lt;/code&gt; in &lt;code&gt;claudemd.ts&lt;/code&gt; (1,479 lines, L790-1074) loads CLAUDE.md through a 6-stage hierarchy:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Stage&lt;/th&gt;
 &lt;th&gt;Source&lt;/th&gt;
 &lt;th&gt;Path Example&lt;/th&gt;
 &lt;th&gt;Priority&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;Org policy&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;/etc/claude-code/CLAUDE.md&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Lowest&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2. User&lt;/td&gt;
 &lt;td&gt;Personal habits&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;Project rules&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; and &lt;code&gt;.claude/rules/*.md&lt;/code&gt; from cwd to root&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;Local overrides&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;CLAUDE.local.md&lt;/code&gt; (gitignored)&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;Auto memory&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;MEMORY.md&lt;/code&gt; entrypoint&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;Team memory&lt;/td&gt;
 &lt;td&gt;Cross-org sync&lt;/td&gt;
 &lt;td&gt;Highest&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Why this order?&lt;/strong&gt; The file comment (L9) states explicitly: &amp;ldquo;Files are loaded in reverse order of priority.&amp;rdquo; LLMs pay more attention to later parts of the prompt, so the most specific instructions (Local &amp;gt; Project &amp;gt; User &amp;gt; Managed) are &lt;strong&gt;placed last&lt;/strong&gt;. This is not CSS specificity — it&amp;rsquo;s a design that leverages &lt;strong&gt;LLM attention bias&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="upward-directory-traversal-and-deduplication"&gt;Upward Directory Traversal and Deduplication
&lt;/h3&gt;&lt;p&gt;Starting from &lt;code&gt;originalCwd&lt;/code&gt;, it walks up to the filesystem root, then calls &lt;code&gt;dirs.reverse()&lt;/code&gt; to process &lt;strong&gt;from root downward&lt;/strong&gt; (L851-857). In monorepos, the parent &lt;code&gt;CLAUDE.md&lt;/code&gt; loads first and the child project&amp;rsquo;s &lt;code&gt;CLAUDE.md&lt;/code&gt; layers on top.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Worktree deduplication&lt;/strong&gt; (L868-884): When a git worktree is nested inside the main repo, an &lt;code&gt;isNestedWorktree&lt;/code&gt; check prevents the same &lt;code&gt;CLAUDE.md&lt;/code&gt; from being loaded twice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;@include directive&lt;/strong&gt; (L451-535): Lexes markdown tokens to ignore &lt;code&gt;@path&lt;/code&gt; inside code blocks, recursively resolving only &lt;code&gt;@path&lt;/code&gt; in text nodes. Maximum depth of 5.&lt;/p&gt;
&lt;h2 id="5-systemuser-context-separation--dual-memoize-cache"&gt;5. System/User Context Separation &amp;ndash; dual-memoize Cache
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;context.ts&lt;/code&gt; (189 lines) separates the system prompt into &lt;strong&gt;two independent contexts&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 state, cache breaker&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;getUserContext()&lt;/code&gt;&lt;/strong&gt; (L155): CLAUDE.md merged string, current date&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Why split into two?&lt;/strong&gt; Because of the Anthropic API&amp;rsquo;s prompt caching strategy. Git state (session-fixed) and CLAUDE.md (invalidated only on file changes) have different cache lifetimes, so &lt;code&gt;cache_control&lt;/code&gt; must be applied differently. Both functions are wrapped in &lt;code&gt;memoize&lt;/code&gt; and execute only once per session.&lt;/p&gt;
&lt;h3 id="3-cache-invalidation-paths"&gt;3 Cache Invalidation Paths
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;setSystemPromptInjection()&lt;/code&gt; (context.ts:29): Clears both caches&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clearMemoryFileCaches()&lt;/code&gt; (claudemd.ts:1119): Clears memory files only&lt;/li&gt;
&lt;li&gt;&lt;code&gt;resetGetMemoryFilesCache()&lt;/code&gt; (claudemd.ts:1124): Clears memory files + fires &lt;code&gt;InstructionsLoaded&lt;/code&gt; hook&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This separation distinguishes between worktree switches (no reload needed) and actual reloads (after compaction).&lt;/p&gt;
&lt;h2 id="6-token-budget--response-continuation-decisions"&gt;6. Token Budget &amp;ndash; Response Continuation Decisions
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;checkTokenBudget()&lt;/code&gt; in &lt;code&gt;tokenBudget.ts&lt;/code&gt; (93 lines) controls &lt;strong&gt;whether to continue responding, not prompt size&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 -- continue if below 90%
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DIMINISHING_THRESHOLD = 500 -- 3+ consecutive turns, &amp;lt;500 tokens each -&amp;gt; diminishing returns
&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;Why 0.9?&lt;/strong&gt; Models tend to start summarizing near the budget limit. Stopping at 90% prevents &amp;ldquo;wrapping up&amp;rdquo; summaries and keeps the work going. The &lt;code&gt;nudgeMessage&lt;/code&gt; explicitly instructs &amp;ldquo;do not summarize.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Diminishing returns detection prevents the model from falling into repetitive patterns. &lt;strong&gt;Sub-agents stop immediately&lt;/strong&gt; (L51) — they don&amp;rsquo;t have their own budgets.&lt;/p&gt;
&lt;h2 id="rust-comparison"&gt;Rust Comparison
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Aspect&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;Hook event types&lt;/td&gt;
 &lt;td&gt;26+&lt;/td&gt;
 &lt;td&gt;PreToolUse, PostToolUse (2 only)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Hook execution&lt;/td&gt;
 &lt;td&gt;Async AsyncGenerator&lt;/td&gt;
 &lt;td&gt;Synchronous &lt;code&gt;Command::output()&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Hook results&lt;/td&gt;
 &lt;td&gt;7 yield variants + JSON&lt;/td&gt;
 &lt;td&gt;Allow/Deny/Warn (3 via exit code)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Input modification&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;hookUpdatedInput&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Not possible&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;allow != bypass&lt;/td&gt;
 &lt;td&gt;Guaranteed&lt;/td&gt;
 &lt;td&gt;Not implemented (security vulnerability)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;CLAUDE.md&lt;/td&gt;
 &lt;td&gt;6-stage discovery&lt;/td&gt;
 &lt;td&gt;4 candidates per dir&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;@include&lt;/td&gt;
 &lt;td&gt;Recursive, depth 5&lt;/td&gt;
 &lt;td&gt;Not supported&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Token budget&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;checkTokenBudget()&lt;/code&gt; with 3 decisions&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Prompt cache&lt;/td&gt;
 &lt;td&gt;memoize + 3 invalidation paths&lt;/td&gt;
 &lt;td&gt;Rebuilt every time&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="insights"&gt;Insights
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The dual meaning of &amp;ldquo;hook&amp;rdquo; is the biggest source of architectural confusion&lt;/strong&gt; &amp;ndash; The 85 React hooks are not in scope for Rust reimplementation. Only the runtime hooks (~5,600 lines) are porting targets. However, this runtime engine includes 26 event types, an async protocol (&lt;code&gt;{&amp;quot;async&amp;quot;:true}&lt;/code&gt; background switching), and prompt requests (bidirectional stdin/stdout). Precisely scoping the meaning of &amp;ldquo;hooks&amp;rdquo; is the starting point for accurate estimation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CLAUDE.md&amp;rsquo;s &amp;ldquo;last is strongest&amp;rdquo; pattern is deliberate exploitation of LLM attention bias&lt;/strong&gt; &amp;ndash; In the 6-stage hierarchical loading (Managed -&amp;gt; User -&amp;gt; Project -&amp;gt; Local -&amp;gt; AutoMem -&amp;gt; TeamMem), the most specific instructions are placed at the end of the prompt for maximum influence. This design emerges at the intersection of &lt;strong&gt;API prompt cache hit-rate optimization + LLM behavioral characteristics&lt;/strong&gt;, not from architectural tidiness.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The &amp;ldquo;allow != bypass&amp;rdquo; invariant in &lt;code&gt;resolveHookPermissionDecision()&lt;/code&gt; is the security cornerstone&lt;/strong&gt; &amp;ndash; The current Rust hooks.rs judges allow/deny solely by exit code. Without implementing JSON result parsing and the subsequent &lt;code&gt;checkRuleBasedPermissions&lt;/code&gt; check, a malicious hook could bypass deny rules — a security vulnerability. Clearly delineating the boundary between automation convenience and security policy is the fundamental challenge of the hook system.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Next post: &lt;a class="link" href="https://ice-ice-bear.github.io/posts/2026-04-06-harness-anatomy-5/" &gt;#5 &amp;ndash; MCP Services and the Plugin-Skill Extension Ecosystem&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Claude Code Harness Anatomy #5 — MCP Services and the Plugin-Skill Extension Ecosystem</title><link>https://ice-ice-bear.github.io/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/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 Harness Anatomy #5 — MCP Services and the Plugin-Skill Extension Ecosystem" /&gt;&lt;h2 id="overview"&gt;Overview
&lt;/h2&gt;&lt;p&gt;Beyond its 42 built-in tools, Claude Code can extend with unlimited external tools via MCP (Model Context Protocol). This post analyzes the connection management architecture of &lt;code&gt;client.ts&lt;/code&gt; (3,348 lines), the OAuth authentication system of &lt;code&gt;auth.ts&lt;/code&gt; (2,465 lines), the 4-layer security model, and config deduplication. We then dissect the structural differences between plugins and skills, the 5-layer skill discovery engine, and the circular reference resolution pattern in &lt;code&gt;mcpSkillBuilders.ts&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="1-mcp-client--connection-management-is-harder-than-the-protocol"&gt;1. MCP Client &amp;ndash; Connection Management Is Harder Than the Protocol
&lt;/h2&gt;&lt;h3 id="memoization-based-connection-pool"&gt;Memoization-Based Connection Pool
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;connectToServer&lt;/code&gt; is wrapped with &lt;code&gt;lodash.memoize&lt;/code&gt;. The cache key is &lt;code&gt;name + JSON(config)&lt;/code&gt;. Since MCP servers are stateful (stdio processes, WebSocket connections), creating a new connection for every tool call would be catastrophically bad for performance.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;onclose&lt;/code&gt; handler invalidates the cache -&amp;gt; next call automatically reconnects&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fetchToolsForClient&lt;/code&gt; and &lt;code&gt;fetchResourcesForClient&lt;/code&gt; each have their own LRU cache (20 entries)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tool-proxy-pattern"&gt;Tool Proxy Pattern
&lt;/h3&gt;&lt;p&gt;MCP tools are converted to native &lt;code&gt;Tool&lt;/code&gt; interfaces:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt;: Format &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;: Always &lt;code&gt;passthrough&lt;/code&gt; — MCP tools use a separate permission system&lt;/li&gt;
&lt;li&gt;&lt;code&gt;annotations&lt;/code&gt;: Maps MCP annotations like &lt;code&gt;readOnlyHint&lt;/code&gt;, &lt;code&gt;destructiveHint&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;URL Elicitation Retry&lt;/strong&gt;: OAuth-based MCP servers can require authentication mid-tool-call (error code -32042). A retry loop shows the user the URL, waits for authentication to complete, and retries.&lt;/p&gt;
&lt;h3 id="connection-state-machine-and-3-strike-terminal-error"&gt;Connection State Machine and 3-Strike Terminal Error
&lt;/h3&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;stateDiagram-v2
 [*] --&gt; Pending: Config loaded
 Pending --&gt; Connected: connectToServer success
 Pending --&gt; Failed: Connection timeout
 Pending --&gt; NeedsAuth: 401 UnauthorizedError
 Pending --&gt; Disabled: isMcpServerDisabled()

 Connected --&gt; Connected: Tool call success
 Connected --&gt; Failed: 3 consecutive terminal errors
 Connected --&gt; NeedsAuth: 401 during callMCPTool
 Connected --&gt; Pending: onclose cache invalidation

 NeedsAuth --&gt; Pending: Auth completed
 NeedsAuth --&gt; NeedsAuth: 15-min TTL cache

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

 note right of Connected
 Exists in memoize cache
 fetchTools/Resources also cached
 end note&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;3-strike rule&lt;/strong&gt;: 3 consecutive terminal errors force a transition to &lt;code&gt;Failed&lt;/code&gt; state. This prevents endlessly retrying against dead servers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;15-minute needs-auth cache&lt;/strong&gt;: Retrying a server that returned 401 every time would cause 30+ connectors to fire simultaneous network requests. The TTL cache prevents unnecessary retries.&lt;/p&gt;
&lt;h2 id="2-oauth--the-reality-of-2465-lines"&gt;2. OAuth &amp;ndash; The Reality of 2,465 Lines
&lt;/h2&gt;&lt;p&gt;The reason &lt;code&gt;auth.ts&lt;/code&gt; is 2,465 lines is that &lt;strong&gt;real-world OAuth servers don&amp;rsquo;t consistently implement the RFCs&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Component&lt;/th&gt;
 &lt;th&gt;Description&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;RFC 9728 + 8414 discovery&lt;/td&gt;
 &lt;td&gt;Server can run AS on a separate host -&amp;gt; discover AS URL via PRM&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;PKCE&lt;/td&gt;
 &lt;td&gt;Public client — code_verifier/code_challenge required&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;XAA (Cross-App Access)&lt;/td&gt;
 &lt;td&gt;Exchange IdP id_token for access_token at the MCP server&amp;rsquo;s AS&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Non-standard error normalization&lt;/td&gt;
 &lt;td&gt;Slack returns HTTP 200 with &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 storage&lt;/td&gt;
 &lt;td&gt;macOS Keychain integration (&lt;code&gt;getSecureStorage()&lt;/code&gt;)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Rust porting implications: OAuth is not an SDK dependency but a &lt;strong&gt;complex async state machine&lt;/strong&gt;. Discovery (2 stages) -&amp;gt; PKCE -&amp;gt; callback server -&amp;gt; token storage -&amp;gt; refresh -&amp;gt; revocation -&amp;gt; XAA. Porting the whole thing is impractical, so starting with stdio MCP + API key authentication is realistic.&lt;/p&gt;
&lt;h2 id="3-4-layer-security-model"&gt;3. 4-Layer Security Model
&lt;/h2&gt;&lt;p&gt;MCP security is not a single gate but a &lt;strong&gt;composition of trust levels&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;If present, blocks all other sources"]
 E2["denylist / allowlist&amp;lt;br/&amp;gt;name, command, URL patterns"]
 end

 subgraph L2["2. Project"]
 P1[".mcp.json loaded"]
 P2["pending -&gt; user approval -&gt; approved"]
 end

 subgraph L3["3. Server"]
 S1["Independent OAuth tokens per server"]
 S2["Keychain storage"]
 end

 subgraph L4["4. Channel"]
 C1["GrowthBook allowlist&amp;lt;br/&amp;gt;tengu_harbor_ledger"]
 C2["Structured events&amp;lt;br/&amp;gt;not plain text matching"]
 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;Each layer operates independently, and &lt;strong&gt;Enterprise takes highest priority&lt;/strong&gt;. Even if &lt;code&gt;.mcp.json&lt;/code&gt; exists in the project, it&amp;rsquo;s blocked if it hits the enterprise denylist.&lt;/p&gt;
&lt;h3 id="config-sources-and-deduplication-configts-1578-lines"&gt;Config Sources and Deduplication (config.ts 1,578 lines)
&lt;/h3&gt;&lt;p&gt;Config source priority (higher wins):&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 (per-user project settings)&lt;/li&gt;
&lt;li&gt;User (global &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 (dynamic)&lt;/li&gt;
&lt;li&gt;claude.ai connectors (lowest)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Why is deduplication needed?&lt;/strong&gt; The same MCP server can exist in both &lt;code&gt;.mcp.json&lt;/code&gt; and claude.ai connectors. &lt;code&gt;getMcpServerSignature&lt;/code&gt; creates &lt;code&gt;stdio:[command|args]&lt;/code&gt; or &lt;code&gt;url:&amp;lt;base&amp;gt;&lt;/code&gt; signatures, unwrapping CCR proxy URLs to original vendor URLs before comparison.&lt;/p&gt;
&lt;p&gt;Environment variable expansion: Supports &lt;code&gt;${VAR}&lt;/code&gt; and &lt;code&gt;${VAR:-default}&lt;/code&gt; syntax. Missing variables are reported as warnings rather than errors to prevent partial connection failures.&lt;/p&gt;
&lt;h2 id="4-plugins-vs-skills--structural-differences"&gt;4. Plugins vs Skills &amp;ndash; Structural Differences
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Dimension&lt;/th&gt;
 &lt;th&gt;Skills&lt;/th&gt;
 &lt;th&gt;Plugins&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Essence&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Prompt extension (SKILL.md = text)&lt;/td&gt;
 &lt;td&gt;System extension (skills + hooks + MCP)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Installation&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Drop a single file&lt;/td&gt;
 &lt;td&gt;Marketplace git clone&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Runtime code&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;None (pure text)&lt;/td&gt;
 &lt;td&gt;Yes (MCP servers, hook scripts)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Toggle&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Implicit (file existence)&lt;/td&gt;
 &lt;td&gt;Explicit (&lt;code&gt;/plugin&lt;/code&gt; UI)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;ID scheme&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;File path&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;{name}@builtin&lt;/code&gt; or &lt;code&gt;{name}@marketplace&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Skills are the embodiment of the &amp;ldquo;file = extension&amp;rdquo; principle. A single &lt;code&gt;SKILL.md&lt;/code&gt; works as an extension immediately without installation or building.&lt;/p&gt;
&lt;h3 id="plugin-service-separation-of-concerns"&gt;Plugin Service Separation of Concerns
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;File&lt;/th&gt;
 &lt;th&gt;Role&lt;/th&gt;
 &lt;th&gt;Side Effects&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;Pure library functions&lt;/td&gt;
 &lt;td&gt;None&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 wrappers&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;process.exit&lt;/code&gt;, console output&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;Background coordinator&lt;/td&gt;
 &lt;td&gt;AppState updates&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The pure functions in &lt;code&gt;pluginOperations&lt;/code&gt; are reused by both CLI and interactive UI.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Marketplace coordination&lt;/strong&gt;: &lt;code&gt;diffMarketplaces()&lt;/code&gt; compares declared marketplaces against actual installations. New installs trigger auto-refresh; existing updates only set a &lt;code&gt;needsRefresh&lt;/code&gt; flag. New installs need auto-refresh to prevent &amp;ldquo;plugin not found&amp;rdquo; errors, while updates let users choose when to apply.&lt;/p&gt;
&lt;h2 id="5-5-layer-skill-discovery-engine"&gt;5. 5-Layer Skill Discovery Engine
&lt;/h2&gt;&lt;p&gt;Loading source priority in &lt;code&gt;loadSkillsDir.ts&lt;/code&gt; (1,086 lines):&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 subgraph Discovery["Skill 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;project root to home"]
 D["4. --add-dir&amp;lt;br/&amp;gt;additional directories"]
 E["5. legacy&amp;lt;br/&amp;gt;/commands/ directory"]
 end

 subgraph Dedup["Deduplication"]
 F["realpath() symlink resolution"]
 G["File ID based first-wins"]
 end

 subgraph Parse["Frontmatter Parsing"]
 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="frontmatter-system"&gt;Frontmatter System
&lt;/h3&gt;&lt;p&gt;15+ fields are extracted from &lt;code&gt;SKILL.md&lt;/code&gt;&amp;rsquo;s YAML frontmatter:&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;: Used by the model for skill selection&lt;/li&gt;
&lt;li&gt;&lt;code&gt;allowed-tools&lt;/code&gt;: List of tools permitted during skill execution&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model&lt;/code&gt;: Force a specific model&lt;/li&gt;
&lt;li&gt;&lt;code&gt;context: fork&lt;/code&gt;: Execute in a separate context&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hooks&lt;/code&gt;: Skill-specific hook configuration&lt;/li&gt;
&lt;li&gt;&lt;code&gt;paths&lt;/code&gt;: Path-based activation filter&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shell&lt;/code&gt;: Inline shell command execution&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="lazy-disk-extraction-of-bundled-skills"&gt;Lazy Disk Extraction of Bundled Skills
&lt;/h3&gt;&lt;p&gt;17 bundled skills compiled into the CLI binary (&lt;code&gt;skills/bundled/&lt;/code&gt;) are &lt;strong&gt;extracted to disk on first invocation&lt;/strong&gt; if they have a &lt;code&gt;files&lt;/code&gt; field:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;O_NOFOLLOW | O_EXCL&lt;/code&gt; flags prevent symlink attacks&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0o600&lt;/code&gt; permissions restrict access&lt;/li&gt;
&lt;li&gt;&lt;code&gt;resolveSkillFilePath()&lt;/code&gt; rejects &lt;code&gt;..&lt;/code&gt; paths to prevent directory escape&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Why extract to disk?&lt;/strong&gt; So the model can read reference files using the &lt;code&gt;Read&lt;/code&gt;/&lt;code&gt;Grep&lt;/code&gt; tools. Keeping them only in memory would make them inaccessible to the model.&lt;/p&gt;
&lt;h3 id="mcpskillbuilders--a-44-line-circular-reference-solution"&gt;mcpSkillBuilders &amp;ndash; A 44-Line Circular Reference Solution
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;mcpSkillBuilders.ts&lt;/code&gt; (44 lines) is small but architecturally significant.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: &lt;code&gt;mcpSkills.ts&lt;/code&gt; needs functions from &lt;code&gt;loadSkillsDir.ts&lt;/code&gt;, but a direct import creates a circular reference (&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;Solution&lt;/strong&gt;: A write-once registry. &lt;code&gt;loadSkillsDir.ts&lt;/code&gt; registers functions at module initialization time, and &lt;code&gt;mcpSkills.ts&lt;/code&gt; retrieves them when needed. Dynamic imports fail in the Bun bundler, and literal dynamic imports trigger dependency-cruiser&amp;rsquo;s circular dependency check, making &lt;strong&gt;this approach the only viable solution&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Leaf modules in the dependency graph import only types, and runtime registration happens exactly once at startup.&lt;/p&gt;
&lt;h2 id="rust-comparison"&gt;Rust Comparison
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Area&lt;/th&gt;
 &lt;th&gt;TS (Complete)&lt;/th&gt;
 &lt;th&gt;Rust (Current)&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Name normalization&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; — same logic&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Server signature&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; — includes CCR proxy unwrap&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;stdio JSON-RPC&lt;/td&gt;
 &lt;td&gt;SDK-dependent&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;mcp_stdio.rs&lt;/code&gt; — direct implementation (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-line full implementation&lt;/td&gt;
 &lt;td&gt;None — types only&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Connection management&lt;/td&gt;
 &lt;td&gt;memoize + onclose reconnection&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Skill loading&lt;/td&gt;
 &lt;td&gt;5-layer + 15-field frontmatter&lt;/td&gt;
 &lt;td&gt;2 directories, SKILL.md only&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Bundled skills&lt;/td&gt;
 &lt;td&gt;17 built-in&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Plugins&lt;/td&gt;
 &lt;td&gt;Built-in + marketplace&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Security&lt;/td&gt;
 &lt;td&gt;4-layer (Enterprise-&amp;gt;Channel)&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Key gap&lt;/strong&gt;: Rust has implemented bootstrap (config -&amp;gt; transport) and stdio JSON-RPC. The SDK-less JSON-RPC implementation in &lt;code&gt;mcp_stdio.rs&lt;/code&gt; is meaningful progress. However, OAuth, connection lifecycle, channel security, and the full skill discovery system are all absent.&lt;/p&gt;
&lt;h2 id="insights"&gt;Insights
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MCP is not a &amp;ldquo;protocol&amp;rdquo; but an &amp;ldquo;integration framework&amp;rdquo;&lt;/strong&gt; &amp;ndash; What &lt;code&gt;client.ts&lt;/code&gt;&amp;rsquo;s 3,348 lines tell us is that the hard part is not JSON-RPC but &lt;strong&gt;connection lifecycle management&lt;/strong&gt;. Memoization, auto-reconnect, session expiry detection, 401 retry, 3-strike terminal errors, needs-auth caching. External processes (stdio) and remote services (HTTP/SSE) die unpredictably, OAuth tokens expire, and networks drop. This is code that reflects the reality that &amp;ldquo;connect once and done&amp;rdquo; doesn&amp;rsquo;t exist.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Skills embody the &amp;ldquo;file = extension&amp;rdquo; principle&lt;/strong&gt; &amp;ndash; A single SKILL.md works as an extension immediately without installation or building. This simplicity, combined with incremental complexity via frontmatter (model specification, hooks, path filters), accommodates both beginners and power users. Plugins are the organizational layer above skills, packaging &amp;ldquo;skills + hooks + MCP servers&amp;rdquo; together.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;mcpSkillBuilders.ts&lt;/code&gt; is a 44-line architecture lesson&lt;/strong&gt; &amp;ndash; The only solution that simultaneously satisfies Bun bundler&amp;rsquo;s dynamic import constraints and dependency-cruiser&amp;rsquo;s circular dependency check was a &amp;ldquo;write-once registry.&amp;rdquo; The pattern where leaf modules import only types and runtime registration happens once at startup is a broadly applicable approach to resolving circular references in complex module systems — worth remembering.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Next post: &lt;a class="link" href="https://ice-ice-bear.github.io/posts/2026-04-06-harness-anatomy-6/" &gt;#6 &amp;ndash; Beyond Claude Code: A Retrospective on Building an Independent 7-Crate Harness&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Claude Code Harness Anatomy #6 — Beyond Claude Code: A Retrospective on Building an Independent 7-Crate Harness</title><link>https://ice-ice-bear.github.io/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/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 Harness Anatomy #6 — Beyond Claude Code: A Retrospective on Building an Independent 7-Crate Harness" /&gt;&lt;h2 id="overview"&gt;Overview
&lt;/h2&gt;&lt;p&gt;This is the final post in the series that systematically dissected Claude Code&amp;rsquo;s TypeScript source across 27 sessions. In Phase 1 we understood the architecture of 100k+ lines of TS code, in Phase 2 we reimplemented core patterns in Rust, and in Phase 3 we designed and built an independent agent harness that overcomes the 8 limitations we discovered. This post covers the limitation analysis, 5 design principles, 7-crate architecture, 61 tests, and a full retrospective of the journey.&lt;/p&gt;
&lt;h2 id="1-8-limitations-of-claude-codes-architecture"&gt;1. 8 Limitations of Claude Code&amp;rsquo;s Architecture
&lt;/h2&gt;&lt;p&gt;From 27 sessions of analysis, we distinguished strengths from limitations. The strengths (AsyncGenerator pipeline, 3-tier concurrency, hook extensibility, CLAUDE.md discovery, MCP support, self-contained tool interface, 7-path error recovery) represent excellent design. However, the following 8 limitations motivated the independent harness:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;#&lt;/th&gt;
 &lt;th&gt;Limitation&lt;/th&gt;
 &lt;th&gt;Source Session&lt;/th&gt;
 &lt;th&gt;Impact&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 dependency — heavy TUI&lt;/td&gt;
 &lt;td&gt;S08&lt;/td&gt;
 &lt;td&gt;Unnecessary dependency in headless mode&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;Single provider (effectively Anthropic-only)&lt;/td&gt;
 &lt;td&gt;S01&lt;/td&gt;
 &lt;td&gt;Cannot use OpenAI or local models&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;main.tsx 4,683-line monolith&lt;/td&gt;
 &lt;td&gt;S01&lt;/td&gt;
 &lt;td&gt;CLI/REPL/session mixed in one file&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;Synchronous tool execution (Rust port)&lt;/td&gt;
 &lt;td&gt;S03&lt;/td&gt;
 &lt;td&gt;No streaming pipelining&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;td&gt;TS ecosystem-locked plugins&lt;/td&gt;
 &lt;td&gt;S13&lt;/td&gt;
 &lt;td&gt;No language-neutral extensions&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;6&lt;/td&gt;
 &lt;td&gt;85 React hooks mixing UI/runtime&lt;/td&gt;
 &lt;td&gt;S08&lt;/td&gt;
 &lt;td&gt;Dual meaning of &amp;ldquo;hook&amp;rdquo;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;7&lt;/td&gt;
 &lt;td&gt;Implicit prompt caching dependencies&lt;/td&gt;
 &lt;td&gt;S10&lt;/td&gt;
 &lt;td&gt;3 cache invalidation paths are implicit&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;8&lt;/td&gt;
 &lt;td&gt;MCP OAuth 2,465-line complexity&lt;/td&gt;
 &lt;td&gt;S12&lt;/td&gt;
 &lt;td&gt;RFC inconsistency is the root cause&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="2-5-design-principles"&gt;2. 5 Design Principles
&lt;/h2&gt;&lt;p&gt;We established 5 core principles to overcome these limitations:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Principle 1 &amp;ndash; Multi-provider&lt;/strong&gt;: Support Anthropic, OpenAI, and local models (Ollama) through a single abstraction.&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; is a provider-neutral struct that each implementation converts to its own API format.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Principle 2 &amp;ndash; Native async&lt;/strong&gt;: Fully async based on 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; channel forwarding replaces the AsyncGenerator pattern.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Principle 3 &amp;ndash; Module separation&lt;/strong&gt;: Conversation engine, tools, hooks, and prompts are each separate crates. No repeating the &lt;code&gt;main.tsx&lt;/code&gt; monolith.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Principle 4 &amp;ndash; Language-neutral extensions&lt;/strong&gt;: SKILL.md compatibility + MCP servers as plugin units.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Principle 5 &amp;ndash; Full MCP utilization&lt;/strong&gt;: Leveraging not just tools but resources, prompts, and sampling across the full spec.&lt;/p&gt;
&lt;h2 id="3-7-crate-architecture"&gt;3. 7-Crate Architecture
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;graph TD
 CLI["harness-cli&amp;lt;br/&amp;gt;REPL binary"] --&gt; CORE["harness-core&amp;lt;br/&amp;gt;Conversation engine + turn loop"]
 CORE --&gt; PROV["harness-provider&amp;lt;br/&amp;gt;LLM provider abstraction"]
 CORE --&gt; TOOLS["harness-tools&amp;lt;br/&amp;gt;Tool registry + built-in tools"]
 CORE --&gt; HOOKS["harness-hooks&amp;lt;br/&amp;gt;Hook pipeline"]
 CORE --&gt; PROMPT["harness-prompt&amp;lt;br/&amp;gt;CLAUDE.md discovery"]
 CORE --&gt; MCP["harness-mcp&amp;lt;br/&amp;gt;MCP client"]
 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;Core design&lt;/strong&gt;: Only &lt;code&gt;harness-core&lt;/code&gt; depends on other crates. The rest are independent of each other (except &lt;code&gt;harness-mcp&lt;/code&gt; -&amp;gt; &lt;code&gt;harness-tools&lt;/code&gt;). This structure enables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Independent &lt;code&gt;cargo test&lt;/code&gt; for each crate&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;harness-core&lt;/code&gt; changes needed when adding providers&lt;/li&gt;
&lt;li&gt;MCP tools implementing the same &lt;code&gt;Tool&lt;/code&gt; trait as built-in tools&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Crate&lt;/th&gt;
 &lt;th&gt;Core Responsibility&lt;/th&gt;
 &lt;th&gt;Test Count&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 calls, SSE parsing, retries&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;Tool registry, 3-tier concurrency&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;Shell hook execution, deny short-circuit, rewrite chain&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-stage CLAUDE.md, SHA-256 deduplication&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;Conversation engine, &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 transport&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 binary&lt;/td&gt;
 &lt;td&gt;&amp;ndash;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="provider-trait--multi-provider"&gt;Provider Trait &amp;ndash; Multi-Provider
&lt;/h3&gt;&lt;p&gt;The existing Rust port&amp;rsquo;s &lt;code&gt;ApiClient&lt;/code&gt; trait was Anthropic-specific (&lt;code&gt;ApiRequest&lt;/code&gt; with Anthropic fields). The &lt;code&gt;Provider&lt;/code&gt; trait accepts a provider-neutral &lt;code&gt;ProviderRequest&lt;/code&gt; that each implementation converts to its own API format. &lt;code&gt;Box&amp;lt;dyn Provider&amp;gt;&lt;/code&gt; enables runtime fallback chains.&lt;/p&gt;
&lt;h3 id="conversationengine--turn-loop"&gt;ConversationEngine &amp;ndash; Turn Loop
&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;Instead of the existing Rust port&amp;rsquo;s &lt;code&gt;ConversationRuntime&amp;lt;C, T&amp;gt;&lt;/code&gt; generic pattern, we use trait objects. The provider must be swappable at runtime (model fallback), and generics fix the type at compile time, lacking flexibility.&lt;/p&gt;
&lt;h3 id="streaming-tool-execution-pipelining"&gt;Streaming Tool Execution (Pipelining)
&lt;/h3&gt;&lt;p&gt;We solved the biggest constraint of the existing Rust port — &amp;ldquo;collect all SSE events then execute tools&amp;rdquo;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When a &lt;code&gt;ContentBlockStop(ToolUse)&lt;/code&gt; event arrives from &lt;code&gt;EventStream&lt;/code&gt;, forward immediately&lt;/li&gt;
&lt;li&gt;After &lt;code&gt;is_concurrency_safe()&lt;/code&gt; check, parallel processing via &lt;code&gt;tokio::spawn&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Tool execution proceeds while the API is still streaming&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="4-phase-2-retrospective--extending-the-existing-port"&gt;4. Phase 2 Retrospective &amp;ndash; Extending the Existing Port
&lt;/h2&gt;&lt;p&gt;Before Phase 3&amp;rsquo;s independent harness, we extended the existing &lt;code&gt;rust/&lt;/code&gt; prototype in Phase 2:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Sprint&lt;/th&gt;
 &lt;th&gt;Achievement&lt;/th&gt;
 &lt;th&gt;Core Pattern&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;Orchestration module + 3-tier concurrency&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tokio::JoinSet&lt;/code&gt;-based parallel execution&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;S16-S17&lt;/td&gt;
 &lt;td&gt;Tool expansion (19 -&amp;gt; 26)&lt;/td&gt;
 &lt;td&gt;Added Task, PlanMode, AskUser&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;S18-S19&lt;/td&gt;
 &lt;td&gt;Hook execution pipeline&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;Skill discovery&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;.claude/skills/&lt;/code&gt; scan, prompt injection&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Most of Phase 2&amp;rsquo;s code was rewritten in Phase 3. However, the &lt;strong&gt;questions discovered during prototyping&lt;/strong&gt; (&amp;ldquo;Why AsyncGenerator?&amp;rdquo;, &amp;ldquo;Why should tools be unaware of the UI?&amp;rdquo;) determined the final design.&lt;/p&gt;
&lt;h2 id="5-61-tests-and-the-mockprovider-pattern"&gt;5. 61 Tests and the MockProvider Pattern
&lt;/h2&gt;&lt;p&gt;All crates are independently testable. &lt;code&gt;MockProvider&lt;/code&gt; enables verifying the conversation engine&amp;rsquo;s full turn loop without actual API calls:&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="n"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SSE&lt;/span&gt; &lt;span class="n"&gt;parsing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;streams&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="n"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;execution&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="n"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deny&lt;/span&gt; &lt;span class="n"&gt;short&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;circuit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rewrite&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeouts&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="n"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;stage&lt;/span&gt; &lt;span class="n"&gt;discovery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;hash&lt;/span&gt; &lt;span class="n"&gt;deduplication&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="n"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;turn&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;tool&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt; &lt;span class="n"&gt;iterations&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="n"&gt;tests&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="n"&gt;initialization&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;tool&lt;/span&gt; &lt;span class="n"&gt;listing&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-how-phase-1-2-lessons-shaped-the-design"&gt;6. How Phase 1-2 Lessons Shaped the Design
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 subgraph Phase1["Phase 1 -- Understanding"]
 direction TB
 P1A["S02: AsyncGenerator chain"]
 P1B["S05: 42-tool classification"]
 P1C["S08: Runtime vs React hooks"]
 P1D["S10: 6-stage CLAUDE.md"]
 P1E["S12: MCP connection management"]
 P1F["S13: Skills = prompts"]
 end

 subgraph Phase3["Phase 3 -- Independent Harness"]
 direction TB
 P3A["EventStream + mpsc channels"]
 P3B["Tool trait + 3-tier"]
 P3C["HookPipeline (runtime only)"]
 P3D["PromptAssembler separation"]
 P3E["harness-mcp stdio"]
 P3F["SKILL.md compatible"]
 end

 P1A --&gt;|"yield -&gt; tx.send()"| P3A
 P1B --&gt;|"fail-closed defaults"| P3B
 P1C --&gt;|"scope reduction"| P3C
 P1D --&gt;|"cache splitting"| P3D
 P1E --&gt;|"implemented without SDK"| P3E
 P1F --&gt;|"text injection"| P3F

 style Phase1 fill:#e1f5fe
 style Phase3 fill:#fff3e0&lt;/pre&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Lesson&lt;/th&gt;
 &lt;th&gt;Source&lt;/th&gt;
 &lt;th&gt;Design Impact&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-stage state machine&lt;/td&gt;
 &lt;td&gt;S03&lt;/td&gt;
 &lt;td&gt;Async implementation in &lt;code&gt;harness-core&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;QueryDeps&lt;/code&gt; callback DI&amp;rsquo;s type safety limits&lt;/td&gt;
 &lt;td&gt;S03&lt;/td&gt;
 &lt;td&gt;Trait object DI&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;6-layer Bash security chain&lt;/td&gt;
 &lt;td&gt;S06&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;check_permissions()&lt;/code&gt; + hook separation&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Agent = recursive harness instance&lt;/td&gt;
 &lt;td&gt;S06&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;ConversationEngine&lt;/code&gt; reuse&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;ApiClient&lt;/code&gt; sync trait blocks pipelining&lt;/td&gt;
 &lt;td&gt;S03&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;Provider&lt;/code&gt; async trait&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Deny short-circuit + Rewrite chaining&lt;/td&gt;
 &lt;td&gt;S09&lt;/td&gt;
 &lt;td&gt;Identical pattern in &lt;code&gt;HookPipeline&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SHA-256 content hash outperforms path hash&lt;/td&gt;
 &lt;td&gt;S11&lt;/td&gt;
 &lt;td&gt;Content hash in &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-architecture-patterns-learned"&gt;7. Top 10 Architecture Patterns Learned
&lt;/h2&gt;&lt;p&gt;Core architecture patterns extracted from 27 sessions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;AsyncGenerator/Stream pipeline&lt;/strong&gt;: The core abstraction for streaming LLM responses&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3-tier tool concurrency&lt;/strong&gt;: ReadOnly/Write/Dangerous classification balances safety and performance&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ToolSpec + ToolResult duality&lt;/strong&gt;: Separating metadata (for LLM) from execution results&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hook chain execution&lt;/strong&gt;: Deny short-circuit, rewrite chain, independent post-hook transforms&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;6-stage prompt discovery&lt;/strong&gt;: Managed -&amp;gt; user -&amp;gt; project -&amp;gt; local overrides&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP adapter pattern&lt;/strong&gt;: Unifying external protocol tools into the internal Tool trait&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Provider abstraction&lt;/strong&gt;: Swapping Anthropic/OpenAI behind the same interface&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSE incremental parsing&lt;/strong&gt;: Assembling network chunks into event frames&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MockProvider testing&lt;/strong&gt;: Verifying engine behavior with predefined event sequences&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skills = prompts&lt;/strong&gt;: Text injection sufficient instead of complex plugin systems&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="8-full-journey-retrospective"&gt;8. Full Journey Retrospective
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Phase&lt;/th&gt;
 &lt;th&gt;Sessions&lt;/th&gt;
 &lt;th&gt;Key Deliverables&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Phase 1 &amp;ndash; Understanding&lt;/td&gt;
 &lt;td&gt;S00-S13&lt;/td&gt;
 &lt;td&gt;14 analysis documents, Rust prototype&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Phase 2 &amp;ndash; Reimplementation&lt;/td&gt;
 &lt;td&gt;S14-S21&lt;/td&gt;
 &lt;td&gt;Orchestration, 26 tools, hooks, skills&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Phase 3 &amp;ndash; Independent Harness&lt;/td&gt;
 &lt;td&gt;S22-S27&lt;/td&gt;
 &lt;td&gt;7-crate workspace, 61+ tests&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Claude Code is a &lt;strong&gt;prompt engineering runtime&lt;/strong&gt;. The core loop assembles messages, the tool system grants the ability to interact with the world, and the permission system sets boundaries. CLAUDE.md injects context, MCP integrates external systems, hooks and agents enable automation/delegation, and plugins/skills transform it into a user extension platform.&lt;/p&gt;
&lt;h3 id="future-directions"&gt;Future Directions
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;True streaming: Processing SSE byte streams chunk by chunk&lt;/li&gt;
&lt;li&gt;Permission system: Per-tool user approval workflows&lt;/li&gt;
&lt;li&gt;MCP SSE transport: HTTP SSE support beyond stdio&lt;/li&gt;
&lt;li&gt;Token budget integration: Automatic context window budget management&lt;/li&gt;
&lt;li&gt;Multi-turn agent mode: Autonomous iteration + breakpoint system&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="insights"&gt;Insights
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Good abstractions emerge at boundaries&lt;/strong&gt; &amp;ndash; Provider trait, Tool trait, HookRunner trait. Every core abstraction is a trait defining module boundaries. The existing Rust port&amp;rsquo;s &lt;code&gt;ConversationRuntime&amp;lt;C, T&amp;gt;&lt;/code&gt; generics provide strong compile-time guarantees but had limitations for scenarios like swapping providers at runtime or dynamically registering MCP tools. &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; trait objects buy runtime flexibility at a minor vtable cost. Relative to LLM API latency (hundreds of ms to seconds), the vtable overhead is immeasurable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The value of prototypes lies in questions, not code&lt;/strong&gt; &amp;ndash; Most of Phase 1-2&amp;rsquo;s prototype code was rewritten in Phase 3. But questions like &amp;ldquo;Why AsyncGenerator?&amp;rdquo;, &amp;ldquo;Why should tools be unaware of UI?&amp;rdquo;, and &amp;ldquo;Why doesn&amp;rsquo;t allow bypass?&amp;rdquo; determined the final design. The act of reading 100k lines of code is not the answer itself — the &lt;strong&gt;design intent (the why)&lt;/strong&gt; discovered during reading is the true deliverable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Most of the TS code&amp;rsquo;s complexity is defensive lines&lt;/strong&gt; &amp;ndash; Permission layers, frontmatter parsing, deduplication, symlink prevention. These aren&amp;rsquo;t features — they&amp;rsquo;re defenses. Rust can guarantee some of this at compile time through its type system and ownership model, but runtime policies like filesystem security and user config precedence must be implemented explicitly. The 27 sessions were the process of mapping these defensive lines, and that map guided the independent harness&amp;rsquo;s design.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Series complete. The full analysis documents are available at the &lt;a class="link" href="https://github.com/lsr/claw-code" target="_blank" rel="noopener"
 &gt;claw-code repository&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</description></item></channel></rss>