<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>CabLate</title><description>AI 時代的軟體開發洞察 - 判斷力才是你的護城河</description><link>https://cablate.com/</link><language>zh-TW</language><item><title>逆向 Claude Agent SDK：找出每則訊息 2-3% 額度消耗的根因與解法</title><link>https://cablate.com/articles/reverse-engineer-claude-agent-sdk-hidden-token-cost/</link><guid isPermaLink="true">https://cablate.com/articles/reverse-engineer-claude-agent-sdk-hidden-token-cost/</guid><description>Agent SDK 的 V1 query() 每次 spawn 新 process 導致 prompt cache 永遠 miss。逆向 cli.js 找到 system-reminder 注入根因，用 V2 persistent session 把 cache efficiency 從 25% 拉到 84%。</description><pubDate>Wed, 25 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;用 Claude Agent SDK 跑多代理系統，每則訊息吃掉 2-3% 的 5 小時額度，訊息內容只有幾個字，成本卻跟寫一整段程式碼差不多。&lt;/p&gt;
&lt;p&gt;同樣的對話在 Claude Code CLI 互動模式下，resume 後第一則 2-3%，第二則起降到 1% 以下。SDK 卻是每一則都 2-3%，沒有例外。&lt;/p&gt;
&lt;p&gt;我花了幾天逆向 SDK 的 12MB minified 原始碼，找到根因，試了一個失敗的修法，最後用另一個方式解決。以下是完整過程。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cablate.com/images/articles/reverse-engineer-claude-agent-sdk-hidden-token-cost/token-cost-comparison.webp&quot; alt=&quot;Agent SDK 與 CLI 互動模式的每訊息 token 消耗對比，SDK 每則 2-3%，CLI 第二則起低於 1%.&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Agent SDK 每次呼叫都在 spawn 新 process&lt;/h2&gt;
&lt;p&gt;SDK 提供一個 &lt;code&gt;query()&lt;/code&gt; 函數（V1 API），你傳入 prompt 跟選項，它在背後做這些事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;spawn&lt;/code&gt; 一個新的 Node.js 子程序，執行 &lt;code&gt;cli.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cli.js&lt;/code&gt; 就是 Claude Code 的完整引擎（12MB minified），跟你在終端機打 &lt;code&gt;claude&lt;/code&gt; 用的是同一份&lt;/li&gt;
&lt;li&gt;如果帶了 &lt;code&gt;resume&lt;/code&gt; 選項，cli.js 會從 JSONL 檔案重建對話歷史&lt;/li&gt;
&lt;li&gt;組裝完整的 API request（system prompt + tools + messages）發給 Anthropic API&lt;/li&gt;
&lt;li&gt;回傳結果後，子程序結束&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;重點是：&lt;strong&gt;每次 &lt;code&gt;query()&lt;/code&gt; 都 spawn 一個全新的 process。&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;你的程式 → sdk.mjs（薄 wrapper）→ spawn node cli.js → Anthropic API
                                    ↑ 每次都是新的 process
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://cablate.com/images/articles/reverse-engineer-claude-agent-sdk-hidden-token-cost/sdk-call-flow.webp&quot; alt=&quot;Agent SDK 每次 query() 的完整呼叫流程，從 sdk.mjs spawn cli.js 到 API request 組裝.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;CLI 互動模式不一樣，你打開終端機之後同一個 cli.js process 會持續存活，直到你關掉它。這個差異是整個問題的根源。&lt;/p&gt;
&lt;p&gt;順帶一提，CLI 也不是完全免疫。如果你在 CLI 做 close 再 resume，第一則訊息一樣要重新建立 cache。對話已經很長的情況下，頻繁 close &amp;amp; resume 等於每次都付一次 cache 重建的成本，所以盡量避免不必要的中斷。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;30 個 GitHub Issues，社群沒有根本解法&lt;/h2&gt;
&lt;p&gt;在開始逆向之前，我先去 GitHub 搜了一下 &lt;code&gt;system reminder&lt;/code&gt; 相關的 issue。結果搜到 30 個 open issues，大致分幾類：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Token 浪費&lt;/strong&gt;（最多人抱怨）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/anthropics/claude-code/issues/16021&quot;&gt;#16021&lt;/a&gt; — 每則 user message 都注入數百行修改檔案備註（23 則留言）&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/anthropics/claude-code/issues/4464&quot;&gt;#4464&lt;/a&gt; — system-reminder 消耗過多 context tokens（22 則留言）&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/anthropics/claude-code/issues/17601&quot;&gt;#17601&lt;/a&gt; — 有人用 mitmproxy 抓到 10,000+ 次隱藏注入，吃掉 15%+ context window&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;安全問題&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/anthropics/claude-code/issues/18560&quot;&gt;#18560&lt;/a&gt; — system-reminder 指示 Claude 不遵守使用者的 CLAUDE.md 設定&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/anthropics/claude-code/issues/31447&quot;&gt;#31447&lt;/a&gt; — Claude 聲稱 system messages 是「被注入的」，社交工程使用者放寬權限&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;功能請求&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/anthropics/claude-code/issues/9769&quot;&gt;#9769&lt;/a&gt; — 請求讓 system-reminder 可個別開關（從 2025-10 開到現在，沒有回應）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;社群沒有根本解法，只有一些 workaround（CLAUDE.md 加忽略指令、第三方工具 Cozempic 等）。所以我決定自己逆向 cli.js 來搞清楚到底怎麼回事。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;逆向 12MB minified cli.js：找到 system-reminder 注入機制&lt;/h2&gt;
&lt;p&gt;cli.js 是一個 12MB 的 minified JavaScript 檔案，所有變數名都被混淆成無意義的短名。我透過字串常量來定位關鍵函數，比如搜尋 &lt;code&gt;&quot;was modified, either by the user&quot;&lt;/code&gt; 就能找到檔案變更注入的函數。&lt;/p&gt;
&lt;p&gt;以下是我逆向出來的關鍵機制。先說結論：system-reminder 注入本身不是成本問題的主因（多注入幾百 tokens 不會讓你每則訊息吃 2-3%），但它是導致 prompt cache 失效的機制，而 cache 失效才是真正吃額度的元兇。理解注入機制是為了理解 cache 為什麼會壞。&lt;/p&gt;
&lt;h3&gt;system-reminder 到底是什麼&lt;/h3&gt;
&lt;p&gt;每次 cli.js 組裝 API request 時，會在 messages 陣列中動態插入一堆 &lt;code&gt;&amp;lt;system-reminder&amp;gt;&lt;/code&gt; 標籤包裹的內容。這些內容：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用 &lt;code&gt;isMeta: true&lt;/code&gt; 標記，所以 UI 上看不到&lt;/li&gt;
&lt;li&gt;不寫進 JSONL 對話歷史，所以事後也查不到&lt;/li&gt;
&lt;li&gt;模板裡寫了 &lt;code&gt;NEVER mention this reminder to the user&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;種類超過 15 種，包括：檔案變更 diff、git status、CLAUDE.md 內容、memory 檔案、task 列表、skill 列表、LSP 診斷等等。每一輪對話都會重新注入。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cablate.com/images/articles/reverse-engineer-claude-agent-sdk-hidden-token-cost/system-reminder-content.webp&quot; alt=&quot;system-reminder 的實際內容，包含 isMeta 標記和 NEVER mention 指令.&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;檔案追蹤表如何觸發注入（readFileState）&lt;/h3&gt;
&lt;p&gt;cli.js 內部維護一張 LRU Cache，記錄「哪些檔案被讀過或寫過」。每輪 user message 時會跑 stale check：遍歷這張表，看檔案的 mtime 有沒有比記錄的 timestamp 新。如果比較新，就算 diff 然後注入。&lt;/p&gt;
&lt;p&gt;觸發注入需要同時滿足這些條件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;檔案在追蹤表裡&lt;/li&gt;
&lt;li&gt;追蹤記錄的 &lt;code&gt;offset&lt;/code&gt; 和 &lt;code&gt;limit&lt;/code&gt; 都是 &lt;code&gt;undefined&lt;/code&gt;（partial read 不追蹤）&lt;/li&gt;
&lt;li&gt;檔案的 mtime &amp;gt; 記錄的 timestamp&lt;/li&gt;
&lt;li&gt;檔案能成功讀取&lt;/li&gt;
&lt;li&gt;diff 不為空&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Agent SDK 為什麼每一輪都觸發注入&lt;/h3&gt;
&lt;p&gt;問題出在 session resume 時的追蹤表重建函數。&lt;/p&gt;
&lt;p&gt;每次 SDK 的 &lt;code&gt;query()&lt;/code&gt; 帶 &lt;code&gt;resume&lt;/code&gt; 呼叫時，cli.js 會從 JSONL 重建這張追蹤表。重建邏輯：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 追蹤表重建的核心收集邏輯（簡化）
for (let block of assistantMessage.content) {
  // 只收集「沒有 offset、沒有 limit」的 Read
  if (block.name === &quot;Read&quot;
      &amp;amp;&amp;amp; block.input.offset === undefined
      &amp;amp;&amp;amp; block.input.limit === undefined) {
    readOps.set(block.id, normalize(block.input.file_path));
  }
  // 收集有 content 的 Write
  if (block.name === &quot;Write&quot;
      &amp;amp;&amp;amp; block.input.file_path
      &amp;amp;&amp;amp; block.input.content) {
    writeOps.set(block.id, { path, content });
  }
  // 不處理 Edit
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重建出來的每筆記錄，&lt;code&gt;offset&lt;/code&gt; 全部是 &lt;code&gt;undefined&lt;/code&gt;（所以全部被追蹤），&lt;code&gt;timestamp&lt;/code&gt; 用的是 JSONL 的過去時間（所以 mtime 幾乎一定比它新）。&lt;/p&gt;
&lt;p&gt;在 CLI 互動模式下，這張表只在 session 開始時建一次，之後的 Read/Write 操作會即時更新它，所以 stale check 在第一輪之後就不再觸發。&lt;/p&gt;
&lt;p&gt;但 SDK 不一樣。每次 &lt;code&gt;query()&lt;/code&gt; 都 spawn 新 process，追蹤表每次都從 JSONL 全量重建。即使上一輪 Claude 有 Read 某個檔案（更新了追蹤記錄），下一輪重建時又全部回到舊狀態。所以&lt;strong&gt;每一輪都會觸發注入&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cablate.com/images/articles/reverse-engineer-claude-agent-sdk-hidden-token-cost/tracking-table-lifecycle.webp&quot; alt=&quot;CLI 與 SDK 的檔案追蹤表生命週期對比，CLI 只建一次之後共用，SDK 每次重建.&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;隱藏注入如何導致 prompt cache 失效&lt;/h2&gt;
&lt;p&gt;這裡是整篇文章的重點。光是注入一些額外內容，不應該造成 2-3% 的額度消耗，多幾百 tokens 的 system-reminder 不是問題。真正的問題是：每次 spawn 新 process → runtime 注入內容重組 → 跟上一次不完全一樣 → 整段 messages 的 prompt cache 失效 → 45k tokens 以 125% 費率重寫。是 cache 失效在燒錢，不是注入本身。&lt;/p&gt;
&lt;p&gt;Anthropic API 有 prompt caching 機制：如果兩次 request 的前綴（system prompt + messages）byte-for-byte 完全一致，API 會走 cache read（只收 10% 費用）。如果不一致，就要 cache write（收 125% 費用）。&lt;/p&gt;
&lt;p&gt;cli.js 的 cache 策略是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;System prompt 的靜態部分加上 &lt;code&gt;cache_control&lt;/code&gt;，可以跨 session 共用&lt;/li&gt;
&lt;li&gt;Messages 只在最後 1-2 則加 &lt;code&gt;cache_control&lt;/code&gt;（滑動窗口）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;問題在於，runtime 注入的 system-reminder 被插在 messages 陣列的&lt;strong&gt;第一個位置&lt;/strong&gt;（claudeMd + currentDate 注入）。從這個位置之後的所有 messages，cache prefix 必須完全一致才能命中。&lt;/p&gt;
&lt;p&gt;SDK 每次 spawn 新 process 時，這些 runtime 注入會被重新組裝。即使語義上一樣（同一天、同樣的 CLAUDE.md），序列化出來的內容可能有微小差異，memory 檔案的 mtime 時間戳、task 列表的順序、git status 的結果等等。只要有一個 byte 不一樣，整段 messages（約 45k tokens）就全部 cache miss，以 125% 費率重新寫入。&lt;/p&gt;
&lt;p&gt;所以實際的成本結構是：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;部分&lt;/th&gt;
&lt;th&gt;大小&lt;/th&gt;
&lt;th&gt;V1 SDK 每次&lt;/th&gt;
&lt;th&gt;CLI 第二次起&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;System prompt&lt;/td&gt;
&lt;td&gt;~15k tokens&lt;/td&gt;
&lt;td&gt;cache read (10%)&lt;/td&gt;
&lt;td&gt;cache read (10%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Messages&lt;/td&gt;
&lt;td&gt;~45k tokens&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;cache write (125%)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;cache read (10%)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;這就是為什麼 SDK 每次 2-3%，CLI 只有 &amp;lt;1%。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cablate.com/images/articles/reverse-engineer-claude-agent-sdk-hidden-token-cost/cache-prefix-break.webp&quot; alt=&quot;SDK V1 的完整流程與 cache 斷裂點，messages[0] 的注入內容每次略有不同導致整段 cache miss.&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Phase 1：逐個消除注入源，A/B 測試證明無效&lt;/h2&gt;
&lt;p&gt;知道了哪些東西在動態注入之後，直覺是把它們一個一個關掉。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CLAUDE_CODE_REMOTE=1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;cli.js 在取 git status 時會檢查這個環境變數，設為 1 就跳過。git status 是最大的動態注入源之一。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JSONL Sanitizer&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 resume 之前預處理 JSONL 檔案，破壞重建函數的收集條件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;給所有 Read 的 &lt;code&gt;input&lt;/code&gt; 加上 &lt;code&gt;offset: 1&lt;/code&gt;（重建函數只收 offset === undefined 的）&lt;/li&gt;
&lt;li&gt;移除所有 Write 的 &lt;code&gt;input.content&lt;/code&gt;（重建函數需要 file_path &amp;amp;&amp;amp; content 才收）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這樣重建出來的追蹤表是空的，不會觸發 file modification 注入。&lt;/p&gt;
&lt;h3&gt;A/B 測試結果&lt;/h3&gt;
&lt;p&gt;同一個對話 session，連續發送短訊息，間隔 11-90 秒：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;條件&lt;/th&gt;
&lt;th&gt;cacheRead&lt;/th&gt;
&lt;th&gt;cacheCreation&lt;/th&gt;
&lt;th&gt;cache efficiency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CLAUDE_CODE_REMOTE=1&lt;/td&gt;
&lt;td&gt;8,624&lt;/td&gt;
&lt;td&gt;48,661&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLAUDE_CODE_REMOTE=1&lt;/td&gt;
&lt;td&gt;11,945&lt;/td&gt;
&lt;td&gt;45,499&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;21%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;無&lt;/td&gt;
&lt;td&gt;15,294&lt;/td&gt;
&lt;td&gt;45,724&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;25%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;無&lt;/td&gt;
&lt;td&gt;15,294&lt;/td&gt;
&lt;td&gt;45,735&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;25%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;cacheRead 穩定在 ~15k（system prompt 的 cache 有命中），但 cacheCreation 穩定在 ~45k（messages 每次都 miss）。即使間隔只有 11 秒，efficiency 也沒有上升。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;結論：消除個別注入源不夠。&lt;/strong&gt; 根本原因是每次 spawn 新 process 後，runtime 注入的重組無法保證 byte-for-byte 一致。只要 messages 的任何位置有差異，整段就 cache miss。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cablate.com/images/articles/reverse-engineer-claude-agent-sdk-hidden-token-cost/ab-test-results.webp&quot; alt=&quot;Phase 1 A/B 測試結果，cache efficiency 始終在 15-25% 之間，沒有改善.&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;用 V2 Persistent Session 解決 prompt cache 問題&lt;/h2&gt;
&lt;p&gt;問題是每次 spawn 新 process，那就不要每次都 spawn。&lt;/p&gt;
&lt;p&gt;SDK 有一個 alpha 階段的 V2 API：&lt;code&gt;unstable_v2_createSession()&lt;/code&gt;。跟 V1 不同，V2 只 spawn 一次 cli.js process，之後的訊息透過 stdin/stdout 在同一個 process 內通訊。process 持續存活，行為就跟 CLI 互動模式一樣。&lt;/p&gt;
&lt;h3&gt;V2 的問題：選項全部硬編碼&lt;/h3&gt;
&lt;p&gt;V2 API 在 v0.2.76 的實作中，很多選項被硬編碼了：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;選項&lt;/th&gt;
&lt;th&gt;V1 query()&lt;/th&gt;
&lt;th&gt;V2 createSession()&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;settingSources&lt;/td&gt;
&lt;td&gt;可自訂&lt;/td&gt;
&lt;td&gt;硬編碼 &lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;systemPrompt&lt;/td&gt;
&lt;td&gt;可自訂&lt;/td&gt;
&lt;td&gt;無&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mcpServers&lt;/td&gt;
&lt;td&gt;可自訂&lt;/td&gt;
&lt;td&gt;硬編碼 &lt;code&gt;{}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cwd&lt;/td&gt;
&lt;td&gt;可自訂&lt;/td&gt;
&lt;td&gt;用 process.cwd()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;thinkingConfig&lt;/td&gt;
&lt;td&gt;可自訂&lt;/td&gt;
&lt;td&gt;硬編碼 void 0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;如果直接用 V2，cli.js 不會載入 CLAUDE.md、不會連接 MCP server、不能指定工作目錄，基本上不能用。&lt;/p&gt;
&lt;h3&gt;Patch SDK 讓 V2 能用在 production&lt;/h3&gt;
&lt;p&gt;我選擇直接 patch &lt;code&gt;sdk.mjs&lt;/code&gt; 中的 &lt;code&gt;SDKSession&lt;/code&gt; class constructor，讓它從傳入的 options 讀取這些值，而不是用硬編碼。&lt;/p&gt;
&lt;p&gt;總共 5 個 patch 點，全部作用在同一個 class 上：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;改動&lt;/th&gt;
&lt;th&gt;說明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;settingSources: []&lt;/code&gt; → &lt;code&gt;options.settingSources ?? []&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;載入 CLAUDE.md 和 settings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;插入 &lt;code&gt;cwd: options.cwd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;指定工作目錄&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;讀取 &lt;code&gt;options.thinkingConfig&lt;/code&gt; / &lt;code&gt;maxTurns&lt;/code&gt; / &lt;code&gt;maxBudgetUsd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;配置 thinking 和限制&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;extraArgs: {}&lt;/code&gt; → &lt;code&gt;options.extraArgs ?? {}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;傳遞額外 CLI 參數&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;從 &lt;code&gt;options.mcpServers&lt;/code&gt; 提取 SDK instance 建立路由 Map&lt;/td&gt;
&lt;td&gt;MCP server in-process routing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Patch 用 postinstall script 自動執行，透過字串常量定位（不依賴行號），每次 &lt;code&gt;npm install&lt;/code&gt; 後自動套用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// patch 的定位方式（不靠行號，靠不會變的字串）
const anchor = &apos;settingSources:[]&apos;;
const replacement = &apos;settingSources:Q.settingSources??[]&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://cablate.com/images/articles/reverse-engineer-claude-agent-sdk-hidden-token-cost/v1-vs-v2-architecture.webp&quot; alt=&quot;V1 query() 與 V2 persistent session 的架構對比，V1 每次 spawn 新 process 導致 cache miss，V2 process 持續存活讓 cache 累積.&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;V2 實測：cache efficiency 從 20% 升到 84%&lt;/h3&gt;
&lt;p&gt;V2 session 保持 cli.js process 存活，cache 跨訊息持續累積：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;訊息 #&lt;/th&gt;
&lt;th&gt;cacheRead&lt;/th&gt;
&lt;th&gt;cacheCreation&lt;/th&gt;
&lt;th&gt;cache efficiency&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;11,689&lt;/td&gt;
&lt;td&gt;45,974&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#2&lt;/td&gt;
&lt;td&gt;69,352&lt;/td&gt;
&lt;td&gt;46,108&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;60%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#3&lt;/td&gt;
&lt;td&gt;127,149&lt;/td&gt;
&lt;td&gt;46,208&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;73%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#4&lt;/td&gt;
&lt;td&gt;402,087&lt;/td&gt;
&lt;td&gt;78,011&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;84%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;第一則訊息跟 V1 一樣要建立 cache（20%），但從第二則起 cache 開始命中，到第四則已經 84%。對比 V1 永遠卡在 25%。&lt;/p&gt;
&lt;p&gt;每訊息成本從 ~1-3% 額度降到穩態 &amp;lt;0.5%。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cablate.com/images/articles/reverse-engineer-claude-agent-sdk-hidden-token-cost/v2-cache-efficiency.webp&quot; alt=&quot;V2 persistent session 的 cache efficiency 逐訊息攀升，對比 V1 永遠卡在 25% 的紅色基線.&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;實際整合 V2 Persistent Session 要注意的事&lt;/h2&gt;
&lt;p&gt;如果你要在自己的專案裡套用這個做法，幾個實務上的考量：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Patch 維護&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Patch 是針對 minified code 做字串替換，不是靠行號。用 postinstall script 在 &lt;code&gt;npm install&lt;/code&gt; 後自動執行就好。每次 SDK 升級後需要驗證 patch 是否還能套用，實際上大約 15-30 分鐘，用錨點字串 grep 新版本的 cli.js / sdk.mjs，確認目標函數還在就行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fallback 策略&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;建議 V2 persistent session 作為主要路徑，V1 &lt;code&gt;query()&lt;/code&gt; 作為 fallback。V2 是 alpha API，不保證穩定，需要有降級機制。新 session（沒有 resume）走 V1 就好，不需要 persistent session。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;記憶體&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;每個 V2 session 是一個持續存活的 node process，約 100-200MB RAM。如果你同時管理很多 session，需要考慮 idle timeout 和 session 回收。Process 被殺或應用重啟後 session 就消失了，下一則訊息需要重新建立 cache。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JSONL Sanitizer 要不要留&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Phase 1 的 JSONL 預處理對 cache efficiency 改善有限（前面 A/B 數據已經證明了），但它能消除 file modification diff 的注入，減少不必要的 context 佔用。作為額外保險可以保留，不是必要。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;現況：等官方解法，先用 patch 頂著&lt;/h2&gt;
&lt;p&gt;如果你在用 V1 &lt;code&gt;query()&lt;/code&gt; + &lt;code&gt;resume&lt;/code&gt;，你的每則訊息都在 cache miss。這不是你的 code 有問題，是 SDK 的架構決定的，每次 spawn 新 process，runtime 注入就會重組，cache prefix 就會斷。&lt;/p&gt;
&lt;p&gt;V2 persistent session 是目前唯一有效的解法，但它還在 alpha，需要 patch 才能用在 production。&lt;/p&gt;
&lt;p&gt;值得關注的是 &lt;a href=&quot;https://github.com/anthropics/claude-code/issues/9769&quot;&gt;#9769&lt;/a&gt;，這個 issue 從 2025-10 開到現在，請求讓 system-reminder 可以個別開關。如果 Anthropic 開放這個功能，或者 V2 API 正式化並補齊選項，上面這些 patch 就不需要了。&lt;/p&gt;
&lt;p&gt;在那之前，監控你的 cache efficiency：&lt;code&gt;cacheReadTokens / (inputTokens + cacheReadTokens + cacheCreationTokens)&lt;/code&gt;，低於 50% 就是在燒錢。&lt;/p&gt;
&lt;p&gt;如果你想自己逆向 cli.js，minified code 每次升級都會重新混淆變數名，但字串常量不會變。搜 &lt;code&gt;&quot;was modified, either by the user&quot;&lt;/code&gt; 找檔案變更注入，搜 &lt;code&gt;&quot;Cannot send to closed session&quot;&lt;/code&gt; 找 V2 Session class，搜 &lt;code&gt;&quot;DISABLE_PROMPT_CACHING&quot;&lt;/code&gt; 找 caching 開關。用這些字串當錨點，就能在任何版本中定位對應函數。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;延伸閱讀&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/articles/claude-code-token-drain-system-reminder/&quot;&gt;我逆向了 Claude Code v2.1.71，找到 token 消耗暴增的根因&lt;/a&gt; — system-reminder diff 注入機制的首次發現&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/articles/agent-sdk-system-reminder-diff-fix/&quot;&gt;Agent SDK diff 注入修復：逆向 C26 函數用 JSONL 預處理解決&lt;/a&gt; — 針對 diff 重複注入的 JSONL 預處理修復方案&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>技術觀點</category><category>Claude Agent SDK</category><category>prompt caching</category><category>逆向工程</category><category>token 成本優化</category><category>Claude Code</category></item><item><title>OpenClaw 爆火讓我不解：AI Agent 這些底層架構一年前就有了</title><link>https://cablate.com/articles/openclaw-agent-fundamentals/</link><guid isPermaLink="true">https://cablate.com/articles/openclaw-agent-fundamentals/</guid><description>李宏毅教授用 OpenClaw 解剖 AI Agent 的五層架構，每一層我都做過。這不是新技術，是舊觀念終於被講清楚。你該學的不是龍蝦，是 Agent 本身。</description><pubDate>Tue, 10 Mar 2026 10:00:00 GMT</pubDate><content:encoded>&lt;p&gt;OpenClaw 爆火的時候，我的反應不是興奮，是略為不解。&lt;/p&gt;
&lt;p&gt;不是說這個專案不好，它把很多概念整合得很完整，社群也經營得不錯。但很多人把它當作「此時此刻的新技術」在追，這讓我有點困惑。&lt;/p&gt;
&lt;p&gt;因為這些東西不是新的。&lt;/p&gt;
&lt;h2&gt;我為什麼覺得 OpenClaw 不是新東西&lt;/h2&gt;
&lt;p&gt;在 ChatGPT 推出 Deep Research 之前，我就自己做過一個 Deep Research。用語言模型拆解問題、呼叫搜尋工具、整合結果、再讓模型歸納，整個流程跟現在大家在講的 Agent 架構一模一樣。&lt;/p&gt;
&lt;p&gt;後來 MCP 出來之後，我基於同樣的原理做了一套多代理架構的 Telegram Bot，叫 ClaudeCab。裡面有秘書、有寫手、有工程師，每個 Agent 有自己的人設、自己的工具、自己的記憶，這套系統到現在還在跑。&lt;/p&gt;
&lt;p&gt;所以當我看到 OpenClaw 的架構介紹，第一個想法是：「這不就是我在做的東西嗎？」&lt;/p&gt;
&lt;h2&gt;李宏毅把底層講清楚了&lt;/h2&gt;
&lt;p&gt;最近李宏毅教授出了一支&lt;a href=&quot;https://youtu.be/2rcJdFuNbZQ?si=ZYmGUdN8jHn3ZM11&quot;&gt;影片&lt;/a&gt;，用 OpenClaw 當解剖對象，把 AI Agent 的運作原理從底層講了一遍。他歸納出五層架構：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;身份注入&lt;/strong&gt;：每次對話前組裝 System Prompt，包含人設、記憶、行為規則&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工具調用&lt;/strong&gt;：語言模型輸出特定格式的文字，外層系統偵測到後執行動作&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;記憶系統&lt;/strong&gt;：短期記憶靠對話歷史，長期記憶寫進檔案每次重讀&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Context 壓縮&lt;/strong&gt;：對話太長就修剪、摘要，但 System Prompt 永遠不壓縮&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自主排程&lt;/strong&gt;：用 Heartbeat 定時喚醒，讓語言模型看起來能「主動行動」&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;影片講得很清楚，推薦想理解 Agent 的人去看。但我想補一個影片沒有強調的角度。&lt;/p&gt;
&lt;h2&gt;這些 Agent 架構從頭到尾沒有變過&lt;/h2&gt;
&lt;p&gt;我一年多前做的東西，跟影片裡拆解的五層架構一模一樣。身份注入、工具調用、記憶讀寫、Context 管理、排程喚醒，全部都有，做法也幾乎一樣。&lt;/p&gt;
&lt;p&gt;那變了什麼？模型更聰明了，工具鏈更方便了，MCP 讓工具管理標準化了，向量資料庫讓記憶檢索更精準了。但這些都是「讓整體管理更輕鬆」的改善，不是架構層面的突破。&lt;/p&gt;
&lt;p&gt;從手排車到自排車，駕駛體驗差很多，但引擎原理沒有變。OpenClaw 就是一台包裝好的自排車，好開，但它沒有重新發明引擎。&lt;/p&gt;
&lt;h2&gt;AI Agent 不是真的有智慧&lt;/h2&gt;
&lt;p&gt;影片裡有一個觀點我非常同意：&lt;strong&gt;AI Agent 不是真的有智慧，它是一套精心設計的「文字把戲」。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;語言模型唯一的功能就是給一段不完整的文字，預測下一個 Token。它沒有記憶，不會主動行動，也無法執行系統操作。你覺得它記得你的偏好？那是因為每次對話前系統都把你的偏好重新塞進去了。你覺得它在你睡覺的時候還在工作？那是排程器定時把它叫醒的。&lt;/p&gt;
&lt;p&gt;「AI 越來越懂你」的真相是：你可以讓 AI 把你這個人分析成一段文字，你的偏好、說話方式、決策邏輯，全部寫進一份檔案，然後每次對話前系統把這份檔案塞給語言模型。它不是真的懂你，它是每次都重新讀一遍「你是誰」。&lt;/p&gt;
&lt;p&gt;李宏毅用了一個很到位的比喻：電影《我的失憶女友》。女友每天記憶重啟，靠日記維持關係。Agent 的 &lt;code&gt;memory.md&lt;/code&gt; 就是那本日記。&lt;/p&gt;
&lt;p&gt;這就是 OpenClaw 在做的事，也是我在 ClaudeCab 裡做的事，也是任何 Agent 框架在做的事。差別不在框架，在你對這套機制理解多深。&lt;/p&gt;
&lt;h2&gt;你要搞懂的不是龍蝦，是 Agent&lt;/h2&gt;
&lt;p&gt;這是我最想講的事。&lt;/p&gt;
&lt;p&gt;如果你今天想用 OpenClaw 做任何東西，你要搞懂的不是 OpenClaw 的介面怎麼操作，而是 Agent 的底層邏輯：System Prompt 怎麼組裝、工具調用怎麼運作、記憶放在哪裡才不會被壓縮掉、Context Window 滿了怎麼辦。&lt;/p&gt;
&lt;p&gt;搞懂這些，你用 OpenClaw 可以，用 &lt;a href=&quot;/articles/claude-code-workflow/&quot;&gt;Claude Code 搭工作流&lt;/a&gt;也可以，自己從 API 寫一個也可以。搞不懂這些，換什麼框架都會遇到一樣的問題。&lt;/p&gt;
&lt;p&gt;我之前寫過一篇&lt;a href=&quot;/articles/blog-writing-openclaw/&quot;&gt;自動化跟 Agent 的差別&lt;/a&gt;，很多人對龍蝦的需求其實只是自動化，根本不需要 Agent 框架，這件事到現在沒有變。&lt;/p&gt;
&lt;h2&gt;不是 OpenClaw 不好，是方向要對&lt;/h2&gt;
&lt;p&gt;我不是在說 OpenClaw 不值得用，它把很多繁瑣的事情包裝好了，降低了入門門檻，這很有價值。&lt;/p&gt;
&lt;p&gt;但如果你只是跟著潮流裝了一個龍蝦，不理解它底下在做什麼，那你遇到問題的時候不會排查，想客製化的時候不知道改哪裡，別的框架出來的時候又要重新學一次。&lt;/p&gt;
&lt;p&gt;李宏毅在影片裡給了一個排查順序，我覺得很實用：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;System Prompt 有沒有正確組裝？&lt;/li&gt;
&lt;li&gt;工具的說明有沒有在 Prompt 裡？&lt;/li&gt;
&lt;li&gt;關鍵指令有沒有寫進 &lt;code&gt;memory.md&lt;/code&gt;？&lt;/li&gt;
&lt;li&gt;Context 有沒有被壓縮掉重要內容？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;這四步跟你用什麼框架無關，它測試的是你對 Agent 運作原理的理解程度。&lt;/p&gt;
&lt;p&gt;下一個爆火的框架一定會來。到時候你是又要從頭學一次，還是看一眼就知道它在做什麼，取決於你現在花時間搞懂的是工具還是概念。&lt;/p&gt;
</content:encoded><category>技術觀點</category><category>OpenClaw</category><category>AI Agent</category><category>Claude Code</category><category>MCP</category><category>Agent 架構</category><category>李宏毅</category></item><item><title>Agent SDK diff 注入修復：逆向 C26 函數用 JSONL 預處理解決</title><link>https://cablate.com/articles/agent-sdk-system-reminder-diff-fix/</link><guid isPermaLink="true">https://cablate.com/articles/agent-sdk-system-reminder-diff-fix/</guid><description>Agent SDK 每次 submitMessage() 都重建 readFileState 導致 diff 重複注入。逆向 C26 函數找到根因，用 JSONL 預處理破壞追蹤條件，附完整 TypeScript 實作。</description><pubDate>Tue, 10 Mar 2026 06:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;/articles/claude-code-token-drain-system-reminder&quot;&gt;前篇&lt;/a&gt;說的是：我逆向了 Claude Code v2.1.71，找到 system-reminder 瘋狂注入 diff 的根因。&lt;/p&gt;
&lt;p&gt;結論是 Agent SDK 用戶幾乎無解。官方 GitHub issue 開了半年沒動靜。&lt;/p&gt;
&lt;p&gt;所以我自己動手修了。這篇說的是我怎麼做的。&lt;/p&gt;
&lt;p&gt;以下分析基於 Claude Code &lt;strong&gt;v2.1.71&lt;/strong&gt;，不同版本行為可能已有不同。&lt;/p&gt;
&lt;h2&gt;Agent SDK 的 readFileState 為什麼跟 CLI 不一樣&lt;/h2&gt;
&lt;p&gt;在 CLI 裡，Edit 之後做一次 Read，下一輪就不會再注入——因為 Read 更新了 readFileState 的 Offset 狀態，讓後續對話認為已同步。&lt;/p&gt;
&lt;p&gt;在 Agent SDK 裡，這個方法完全沒用。&lt;/p&gt;
&lt;p&gt;原因是：&lt;strong&gt;readFileState 不是 class property，是區域變數。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;每次 &lt;code&gt;submitMessage()&lt;/code&gt; 被呼叫，Claude Code 就會跑一個叫 C26 的函數，從 JSONL 重建整張 readFileState 表：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;submitMessage()
  → readFileState = C26(mutableMessages)  ← 從 JSONL 全量重建
  → stale check → diff 注入
  → 主迴圈跑完，Edit/Write 更新了記憶體中的 readFileState
  → 下一次 submitMessage()
  → readFileState = C26(mutableMessages)  ← 又從頭重建，之前的更新全丟
  → 永遠注入
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你在對話過程中做了幾百次 Read，全都是在一個每輪都會被丟棄的記憶體結構上操作。下一輪，C26 拿 JSONL 重新建表，一切歸零。&lt;/p&gt;
&lt;p&gt;CLI 用戶的 readFileState 活在 process 的生命週期裡，修一次就好。Agent SDK 每次 API call 都重建，在對話中怎麼修都沒用。&lt;/p&gt;
&lt;h2&gt;C26 實際上只收集哪些檔案&lt;/h2&gt;
&lt;p&gt;搞清楚為什麼修不了之後，下一步是搞清楚 C26 建表時的邏輯——它到底從 JSONL 裡收集什麼。&lt;/p&gt;
&lt;p&gt;C26 從 tool_use 裡收集兩種東西：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;來源&lt;/th&gt;
&lt;th&gt;收集條件&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read tool&lt;/td&gt;
&lt;td&gt;&lt;code&gt;input.offset === undefined &amp;amp;&amp;amp; input.limit === undefined&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write tool&lt;/td&gt;
&lt;td&gt;&lt;code&gt;input.file_path &amp;amp;&amp;amp; input.content&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Edit tool 不在收集範圍——C26 不處理 Edit。&lt;/p&gt;
&lt;p&gt;這裡有個反直覺的發現：&lt;strong&gt;Read 工具預設傳 &lt;code&gt;offset=1&lt;/code&gt;，所以它幾乎永遠不會被 C26 收進追蹤表。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;真正會被追蹤的是：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;來源&lt;/th&gt;
&lt;th&gt;offset&lt;/th&gt;
&lt;th&gt;limit&lt;/th&gt;
&lt;th&gt;是否被追蹤&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CLAUDE.md / MEMORY.md（預設載入）&lt;/td&gt;
&lt;td&gt;undefined&lt;/td&gt;
&lt;td&gt;undefined&lt;/td&gt;
&lt;td&gt;永遠&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write tool&lt;/td&gt;
&lt;td&gt;undefined&lt;/td&gt;
&lt;td&gt;undefined&lt;/td&gt;
&lt;td&gt;永遠&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C26 從 JSONL 重建的所有 entry&lt;/td&gt;
&lt;td&gt;undefined&lt;/td&gt;
&lt;td&gt;undefined&lt;/td&gt;
&lt;td&gt;永遠&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read tool（offset=1 預設值）&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;undefined&lt;/td&gt;
&lt;td&gt;不追蹤&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;C26 重建所有 entry 時，timestamp 用的是 JSONL 裡記錄的過去時間。檔案的 mtime 幾乎必然比那個時間新，所以每次重建完，stale check 一跑就觸發注入。&lt;/p&gt;
&lt;h2&gt;解法：在 resume 前預處理 JSONL&lt;/h2&gt;
&lt;p&gt;修不了 C26 的邏輯，但可以在它執行前動它的輸入。&lt;/p&gt;
&lt;p&gt;C26 有兩個收集條件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Read 的條件&lt;/strong&gt;：&lt;code&gt;offset === undefined &amp;amp;&amp;amp; limit === undefined&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write 的條件&lt;/strong&gt;：&lt;code&gt;file_path &amp;amp;&amp;amp; content&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;只要在 resume 前把 JSONL 改掉，讓這兩個條件都不成立，C26 就建不出有效的追蹤 entry，注入就不會發生。&lt;/p&gt;
&lt;p&gt;具體做法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read 的 tool_use → 加上 &lt;code&gt;offset: 1&lt;/code&gt;，讓 C26 的收集條件不成立&lt;/li&gt;
&lt;li&gt;Write 的 tool_use → 移除 &lt;code&gt;content&lt;/code&gt;，讓 C26 的收集條件不成立&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這個改法不影響對話內容或 Claude 的理解，只影響 C26 的建表邏輯。&lt;/p&gt;
&lt;h2&gt;實作&lt;/h2&gt;
&lt;h3&gt;第一步：找到 JSONL 路徑&lt;/h3&gt;
&lt;p&gt;JSONL 放在 &lt;code&gt;~/.claude/projects/{project-hash}/{session-id}.jsonl&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;project-hash 的算法是把工作目錄路徑的所有非英數字元替換成 &lt;code&gt;-&lt;/code&gt;（超過 200 字元則截斷後加 charCode hash）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { homedir } from &apos;os&apos;;
import { join } from &apos;path&apos;;

function projectHash(cwd: string): string {
  const result = cwd.replace(/[^a-zA-Z0-9]/g, &apos;-&apos;);
  if (result.length &amp;lt;= 200) return result;

  let h = 0;
  for (let i = 0; i &amp;lt; cwd.length; i++) {
    h = (h &amp;lt;&amp;lt; 5) - h + cwd.charCodeAt(i);
    h |= 0;
  }
  return result.slice(0, 200) + &apos;-&apos; + Math.abs(h).toString(36);
}

function getJsonlPath(cwd: string, sessionId: string): string {
  return join(homedir(), &apos;.claude&apos;, &apos;projects&apos;, projectHash(cwd), `${sessionId}.jsonl`);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;第二步：sanitizeJsonl — 破壞 C26 的收集條件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import { readFileSync, writeFileSync, copyFileSync } from &apos;fs&apos;;

function sanitizeJsonl(jsonlPath: string): { reads: number; writes: number } {
  const stats = { reads: 0, writes: 0 };

  // 備份，改壞了還能救
  copyFileSync(jsonlPath, jsonlPath + &apos;.bak&apos;);

  const content = readFileSync(jsonlPath, &apos;utf-8&apos;);
  const lines = content.split(&apos;\n&apos;).map(line =&amp;gt; {
    if (!line.trim()) return line;

    const msg = JSON.parse(line);
    if (msg.type !== &apos;assistant&apos; || !Array.isArray(msg.message?.content)) {
      return line;
    }

    for (const block of msg.message.content) {
      if (block.type !== &apos;tool_use&apos;) continue;

      // Read without offset → 加 offset=1，讓 C26 的收集條件不成立
      if (block.name === &apos;Read&apos; &amp;amp;&amp;amp; block.input?.offset == null) {
        block.input.offset = 1;
        stats.reads++;
      }

      // Write with content → 移除 content，讓 C26 的收集條件不成立
      if (block.name === &apos;Write&apos; &amp;amp;&amp;amp; &apos;content&apos; in (block.input ?? {})) {
        delete block.input.content;
        stats.writes++;
      }
    }

    return JSON.stringify(msg);
  });

  writeFileSync(jsonlPath, lines.join(&apos;\n&apos;), &apos;utf-8&apos;);
  return stats;
}

export { projectHash, getJsonlPath, sanitizeJsonl };
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;第三步：整合到 Agent SDK&lt;/h3&gt;
&lt;p&gt;在每次 resume 前呼叫：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { sanitizeJsonl, getJsonlPath } from &apos;./sanitize-jsonl&apos;;

const jsonlPath = getJsonlPath(process.cwd(), sessionId);
const { reads, writes } = sanitizeJsonl(jsonlPath);
console.log(`Patched ${reads} Read + ${writes} Write entries`);

const result = query({
  prompt: &apos;continue&apos;,
  options: { cwd: process.cwd(), resume: true, sessionId }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;這個修法的代價&lt;/h2&gt;
&lt;p&gt;說清楚：這是在動 JSONL 原始檔案。&lt;/p&gt;
&lt;p&gt;備份是有的，改壞了可以還原。但這個 fix 的根本假設是「C26 的收集邏輯不變」——如果 Anthropic 哪天改了 C26 的實作，這個 fix 就失效了，而且不一定會立刻發現（還是會注入，不會噴 error）。&lt;/p&gt;
&lt;p&gt;這不是優雅的解法。這是在&lt;a href=&quot;/articles/claude-code-workflow/&quot;&gt;用 Claude Code 建構自動化系統&lt;/a&gt;的過程中，官方沒在動、但問題實際影響開發效率，所以自己能做的事。&lt;/p&gt;
&lt;p&gt;官方 issue 還開著。有進展的話另篇再說。&lt;/p&gt;
</content:encoded><category>技術觀點</category><category>Agent SDK</category><category>Claude Code</category><category>system-reminder</category><category>token 消耗</category><category>JSONL</category></item><item><title>我逆向了 Claude Code v2.1.71，找到 token 消耗暴增的根因</title><link>https://cablate.com/articles/claude-code-token-drain-system-reminder/</link><guid isPermaLink="true">https://cablate.com/articles/claude-code-token-drain-system-reminder/</guid><description>Claude Code v2.1.71 的 system-reminder 機制有個嚴重問題：每次對話都在偷偷注入被修改檔案的完整 diff。我逆向了原始碼找到根因，附上 CLI 緩解方法與 Agent SDK 的無解現況。</description><pubDate>Tue, 10 Mar 2026 02:00:00 GMT</pubDate><content:encoded>&lt;p&gt;這個月 token 額度燒得特別快。&lt;/p&gt;
&lt;p&gt;我有注意到，但一直說服自己是因為最近做的事情比較多。直到上週開始認真懷疑，仔細回想又沒多做什麼事情，才決定認真查這件事。&lt;/p&gt;
&lt;p&gt;查完之後，我的結論是：不是你的錯覺，也不是你真的多做了多少事，是 Claude Code 的 system-reminder 在坑你。&lt;/p&gt;
&lt;p&gt;以下分析是基於 Claude Code &lt;strong&gt;v2.1.71&lt;/strong&gt; 的逆向結果，不同版本行為可能有差異，之後的版本可能修了，也可能沒有。&lt;/p&gt;
&lt;h2&gt;Claude Code 的 system-reminder 究竟注入了什麼&lt;/h2&gt;
&lt;p&gt;先說我怎麼確認這件事的。&lt;/p&gt;
&lt;p&gt;我在對話過程中要求 Claude Code 輸出當次 system-reminder 的完整內容，看看裡面塞了什麼。幾次下來觀察到幾個共同現象：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;變更檔案的&lt;strong&gt;完整 diff&lt;/strong&gt; 被注入進去&lt;/li&gt;
&lt;li&gt;注入的內容被明確標記「不要主動告知使用者」&lt;/li&gt;
&lt;li&gt;Skill 和 Plugin 的 metadata 每次對話都會重新注入&lt;/li&gt;
&lt;li&gt;以上所有內容，&lt;strong&gt;不會出現在 JSONL 裡&lt;/strong&gt;，你沒辦法直接觀測&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最後一點是關鍵。你以為的對話成本只有你看得到的那些，實際上每次背後還夾帶著你看不到的東西——而且 Claude 被明確要求不要主動告訴你。&lt;/p&gt;
&lt;p&gt;結果就是：根本沒聊多少內容，但只要編輯過任何檔案，哪怕只是一句簡單的請求與回應，就直接吃掉 2%-3% 的五小時額度。&lt;/p&gt;
&lt;p&gt;翻了一下 GitHub，這個狀況半年前就有災情了。&lt;/p&gt;
&lt;p&gt;在反覆觀察這個行為的過程中，我注意到一個規律：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;已知編輯過檔案的情況下，發一次訊息 → diff 被注入&lt;/li&gt;
&lt;li&gt;接著先做一次 Read，再發訊息 → diff 沒有被注入&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;推測內部應該有個東西在管理這個狀態，但不知道為什麼在某些情況下亂掉了。於是我讓 Claude Code 去逆向 v2.1.71 的原始碼，搞清楚背後到底是什麼機制。&lt;/p&gt;
&lt;h2&gt;每次發訊息，Claude Code 都做了這件事：readFileState 機制&lt;/h2&gt;
&lt;p&gt;逆向出來的結果，印證了先前的猜測。&lt;/p&gt;
&lt;p&gt;逆向出來是這樣的：Claude Code 內部有一張叫 &lt;code&gt;readFileState&lt;/code&gt; 的表，記錄「我知道的每個檔案長什麼樣」。&lt;/p&gt;
&lt;p&gt;每次你發訊息，Claude Code 會遍歷這張表裡的每個檔案，走一套判斷流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;這個檔案是「完整讀取」的嗎？→ 不是就跳過&lt;/li&gt;
&lt;li&gt;檔案的修改時間比我記錄的新嗎？→ 沒有就跳過&lt;/li&gt;
&lt;li&gt;重新讀一次，跟記錄的內容比對 → 沒差異就跳過&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有差異，或 Offset 沒有狀態&lt;/strong&gt; → 把 diff 當 system-reminder 塞進你的訊息&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;這裡的「Offset 狀態」是 Claude Code 用來記錄「我已經比對過這個版本了」的標記。有這個標記，後續就不會重新注入；沒有這個標記，每次都走注入流程。&lt;/p&gt;
&lt;p&gt;到這裡邏輯看起來沒問題，有差才注入，沒差跳過。&lt;/p&gt;
&lt;p&gt;但接著我看到建表的部分，嗅到問題了。&lt;/p&gt;
&lt;h2&gt;Token 消耗無法收斂的根因：路徑 key 對不起來&lt;/h2&gt;
&lt;p&gt;這張追蹤表存在記憶體裡，關掉就沒了。&lt;/p&gt;
&lt;p&gt;Resume 一段對話之後，Claude Code 會從 JSONL 把這張表還原出來。還原之後，表格裡最多有 10 筆追蹤中的檔案，但這些還原回來的記錄，都&lt;strong&gt;沒有 Offset 狀態&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;沒有 Offset 狀態，回到第 4 條：觸發注入。&lt;/p&gt;
&lt;p&gt;照理說應該是這樣的流程：Resume 後的第一句話觸發注入，注入完畢更新狀態，後續對話就正常了。消耗一次，之後沒事。&lt;/p&gt;
&lt;p&gt;但問題在這張表是用&lt;strong&gt;路徑字串當 key&lt;/strong&gt; 的。&lt;/p&gt;
&lt;p&gt;只要 JSONL 裡記錄的路徑，跟實際讀取時的路徑這兩串字對不起來，追蹤表上那筆記錄永遠不會被更新成功。明明注入了，但表格認不出來，下次還是從頭來。&lt;/p&gt;
&lt;p&gt;那就是每次都走注入流程，永遠不會停。&lt;/p&gt;
&lt;h2&gt;如何減少 Claude Code token 消耗（CLI 用戶）&lt;/h2&gt;
&lt;p&gt;根據 readFileState 的機制，緩解方法也就跑出來了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;任何 Edit 或 Update 之後，主動做一次 Read&lt;/strong&gt;。Read 會強迫更新表的狀態，讓後續對話認為已經同步、不需要再注入了。先多燒一次 Read 的成本，但總比後續每次對話都在注入要好。&lt;/p&gt;
&lt;p&gt;另外，&lt;strong&gt;避免頻繁 Resume&lt;/strong&gt;。每次 Resume 都會重建這張表，觸發一輪注入。如果不需要接續之前的對話脈絡，直接開新的 Session 更划算。&lt;/p&gt;
&lt;p&gt;最後，&lt;strong&gt;整理你的 &lt;code&gt;CLAUDE.md&lt;/code&gt;&lt;/strong&gt;。&lt;code&gt;CLAUDE.md&lt;/code&gt; 和 &lt;code&gt;MEMORY.md&lt;/code&gt; 這類基礎設定檔是預設永遠追蹤的，每次都在清單裡沒辦法跳過。官方後來越來越強調「CLAUDE.md 不要塞太多東西」，我現在終於理解原因了——不是設計哲學，是 token 成本。如果你在用 &lt;a href=&quot;/articles/claude-code-workflow/&quot;&gt;Claude Code 建站或管理專案&lt;/a&gt;，這點格外重要。&lt;/p&gt;
&lt;h2&gt;Agent SDK 用戶的 token 問題為什麼特別嚴重&lt;/h2&gt;
&lt;p&gt;CLI 用戶至少有緩解方法，&lt;a href=&quot;/articles/blog-writing-openclaw/&quot;&gt;用 Agent SDK 搭建自動化流程&lt;/a&gt;的用戶是另一個等級的問題。&lt;/p&gt;
&lt;p&gt;Agent SDK 的架構決定了持續對話必須以 Resume 為主。每次送一筆訊息進去，就是走一次 Resume——重建 readFileState、判斷哪些檔案要注入、注入、送訊息。&lt;/p&gt;
&lt;p&gt;然後下一筆訊息，再來一次。&lt;/p&gt;
&lt;p&gt;不管這段對話裡你讀了幾百次，每次都重建這張表，每次都觸發注入，每次都吃。Agent SDK 設計上就是拿 resume 來維持對話連續性，這個機制根本繞不過去。&lt;/p&gt;
&lt;p&gt;這個問題目前無解。&lt;/p&gt;
&lt;p&gt;偏偏還有兩個問題跑出來讓事情更不好看：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;快取命中失效&lt;/strong&gt;：照理說，相同內容反覆注入應該要命中 prompt cache 來省 token。但有回報指出 SDK 的 cache 運作不正常，反覆注入的內容根本沒有命中快取，每次都是新的費用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TTL 比官方文件寫的貴 50%&lt;/strong&gt;：SDK 執行快取的 TTL 被挖出來是 1 小時留存，比官方文件寫的 5 分鐘版本貴了約 50%。跟快取失效連動起來，是雪上加霜。&lt;/p&gt;
&lt;p&gt;如果你是 Agent SDK 用戶，目前能做的大概只有：&lt;strong&gt;主動讓 AI 輸出當前 system-reminder 中 Data Modified 的篇幅，如果太長，就重開對話&lt;/strong&gt;。沒有比這更好的辦法了。&lt;/p&gt;
&lt;h2&gt;Anthropic 的回應：GitHub issue 半年了還沒動靜&lt;/h2&gt;
&lt;p&gt;GitHub 上的相關 issue 已經存在半年了。&lt;/p&gt;
&lt;p&gt;目前看起來官方是一副「誰理你」的樣子。&lt;/p&gt;
&lt;p&gt;我自己是 Agent SDK 的用戶，這個問題對我的影響直接。研究完這些之後，大概得自己去動 source code 來緩解，等官方修是等不住的。&lt;/p&gt;
&lt;p&gt;如果你用 Claude Code 有遇到類似的額度問題，或是對 system-reminder 的注入機制有更多觀察，歡迎留言。&lt;/p&gt;
</content:encoded><category>技術觀點</category><category>Claude Code</category><category>token 消耗</category><category>system-reminder</category><category>Agent SDK</category><category>AI 工具</category><category>逆向工程</category><category>readFileState</category></item><item><title>AI 取代工作？從 Anthropic 雷達圖看理論與現實的落差</title><link>https://cablate.com/articles/anthropic-radar-ai-cannot-replace/</link><guid isPermaLink="true">https://cablate.com/articles/anthropic-radar-ai-cannot-replace/</guid><description>Anthropic 雷達圖讓很多人焦慮 AI 會取代工作，但理論覆蓋率跟實際取代是兩件事。藍色畫得再大，沒人把它變成紅色，它就只是一張圖。</description><pubDate>Mon, 09 Mar 2026 02:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近看到 Anthropic 發布的一張雷達圖，覺得很值得聊。&lt;/p&gt;
&lt;p&gt;這張圖把各個職業類別的「AI 理論覆蓋率」和「AI 實際使用率」放在一起比較。藍色是理論上 LLM 能執行的任務佔比，紅色是從實際使用數據觀察到的覆蓋範圍。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cablate.com/images/articles/anthropic-radar-ai-cannot-replace/anthropic-radar-chart.webp&quot; alt=&quot;Anthropic 發布的職業類別 AI 理論覆蓋率（藍色）與實際使用率（紅色）對比雷達圖，顯示兩者之間的巨大落差。&quot; /&gt;&lt;/p&gt;
&lt;p&gt;第一眼看到的就是：兩者差距大到不像話。&lt;/p&gt;
&lt;p&gt;Computer &amp;amp; Math、Business &amp;amp; Finance、Architecture &amp;amp; Engineering、Education、Arts &amp;amp; Media——這些領域的藍色幾乎佔滿整個象限，但紅色小到像是在圖上畫了個點。&lt;/p&gt;
&lt;p&gt;很多人看到這張圖，得出的結論是：「AI 已經能做這麼多了，取代只是時間問題。」&lt;/p&gt;
&lt;p&gt;但我想從另一個方向來看。&lt;/p&gt;
&lt;h2&gt;AI「理論覆蓋率」是什麼？和 AI 取代工作差在哪&lt;/h2&gt;
&lt;p&gt;先釐清一件事：理論覆蓋率衡量的是 LLM「在測試環境下能完成某類任務」的比例。&lt;/p&gt;
&lt;p&gt;這跟「在真實工作場景中取代某個角色」是完全不同的事。&lt;/p&gt;
&lt;p&gt;舉個例子。LLM 理論上能寫出一段合規的金流串接程式碼。但在真實場景裡，你需要的不只是一段程式碼——你需要判斷用哪家金流服務、評估手續費結構對商業模式的影響、處理退款流程的邊界情況、確保整個系統符合資安規範。&lt;/p&gt;
&lt;p&gt;LLM 可以處理其中的某些步驟，但「完成任務」跟「取代負責這件事的人」之間，隔著一整層判斷和責任。&lt;/p&gt;
&lt;p&gt;所以當我們說某個領域「理論覆蓋率 80%」，它代表的是「在這個領域裡，80% 的任務 LLM 在技術上可以執行」。它不代表「這個領域 80% 的人會被取代」。&lt;/p&gt;
&lt;p&gt;把這兩件事劃等號，是目前最常見的邏輯跳躍。&lt;/p&gt;
&lt;h2&gt;實際使用率（紅色）告訴我們的事&lt;/h2&gt;
&lt;p&gt;如果理論覆蓋率真的等於取代可能性，那實際使用率應該跟藍色差不多才對。&lt;/p&gt;
&lt;p&gt;但它沒有。&lt;/p&gt;
&lt;p&gt;紅色小得可憐，而且集中在少數領域。這代表即使 AI 技術上能做到，大部分場景下人們還是沒有真的用它來做。&lt;/p&gt;
&lt;p&gt;原因可能有很多：不知道怎麼用、導入成本太高、信任問題、流程還沒準備好、管理層不理解。&lt;/p&gt;
&lt;p&gt;這跟&lt;a href=&quot;/articles/blog-writing-openclaw/&quot;&gt;自動化工具和 AI Agent 的差異&lt;/a&gt;是同一個問題——很多人高估了「技術上可行」和「真正導入」之間的距離。&lt;/p&gt;
&lt;p&gt;但不管原因是什麼，這個落差本身就在告訴我們一件事——技術能力和實際採用之間的距離，遠比我們以為的大。&lt;/p&gt;
&lt;p&gt;而 AI 取代工作發生在「採用」之後，不是在「技術可行」之後。&lt;/p&gt;
&lt;h2&gt;價值轉移到管理層？先看看管理層自己&lt;/h2&gt;
&lt;p&gt;現在主流的論述是這樣的：AI 會取代執行層的工作，所以價值會往上轉移到管理層。未來重要的是判斷力、決策力、統籌能力。&lt;/p&gt;
&lt;p&gt;這個邏輯乍看之下合理。既然 AI 能寫程式、做分析、產報告，那負責「決定要做什麼」和「判斷做得對不對」的人就更有價值了。&lt;/p&gt;
&lt;p&gt;但回頭看這張圖——Management 的理論覆蓋率也不低。Business &amp;amp; Finance 更是接近滿分。&lt;/p&gt;
&lt;p&gt;這就出現了一個矛盾：&lt;/p&gt;
&lt;p&gt;如果我們相信「AI 理論上能做到 = 早晚會被取代」，然後建議大家把價值轉移到管理層——但管理層本身也在 AI 的理論覆蓋範圍內。&lt;/p&gt;
&lt;p&gt;那你轉移到的那個地方，跟你離開的地方，面對的是同一個問題。&lt;/p&gt;
&lt;p&gt;當然，你可以說「現在紅色還很小，管理層還沒被碰到」。沒錯。但如果你接受「藍色遲早會變紅色」這個前提，那管理層也一樣遲早會被碰到。&lt;/p&gt;
&lt;p&gt;你不能在同一個論述裡，用「理論覆蓋率高所以執行層危險」來嚇人，又用「理論覆蓋率高但管理層暫時安全」來安慰人。&lt;/p&gt;
&lt;h2&gt;「往上跑」不是解法：AI 取代沒有在沿著階層爬&lt;/h2&gt;
&lt;p&gt;「價值往上轉移」這個說法，背後有一個隱含假設：工作是階層式的，越上面越不可取代。&lt;/p&gt;
&lt;p&gt;執行層做事，管理層決策。AI 取代做事的，所以決策的更有價值。&lt;/p&gt;
&lt;p&gt;但這個假設成立嗎？&lt;/p&gt;
&lt;p&gt;從這張圖來看，AI 的理論能力不是沿著某個階層往上爬的。它是全面性的——從 Office &amp;amp; Admin 到 Architecture &amp;amp; Engineering 到 Management，它什麼都碰一點。&lt;/p&gt;
&lt;p&gt;它不是從底層開始往上吃。它是同時在所有層級擴展。&lt;/p&gt;
&lt;p&gt;如果是這樣，那「往上跑」就不是解法。因為你跑到的地方，AI 也在那裡。&lt;/p&gt;
&lt;h2&gt;什麼才是真正不會被 AI 取代的&lt;/h2&gt;
&lt;p&gt;我不認為答案是「某個特定的職位」或「某個特定的層級」。&lt;/p&gt;
&lt;p&gt;從這張圖的藍色和紅色之間的落差來看，真正稀缺的其實是：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;能讓紅色靠近藍色的人。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;也就是說——能夠把 AI 的理論能力，轉化為實際可用的工作流程的人。這跟&lt;a href=&quot;/articles/claude-code-workflow/&quot;&gt;用 AI 工具真正完成一個完整專案&lt;/a&gt;的邏輯是一樣的：不是會用工具，而是能駕馭整個流程。&lt;/p&gt;
&lt;p&gt;這跟你是工程師還是管理者無關。這跟你能不能理解問題的結構、設計合理的流程、判斷 AI 做得對不對、在出錯時承擔後果有關。&lt;/p&gt;
&lt;p&gt;專業的核心從來不是「會操作某個工具」，也不是「坐在某個位子上」。&lt;/p&gt;
&lt;p&gt;而是你能不能在複雜的情境中，做出需要承擔後果的判斷。&lt;/p&gt;
&lt;p&gt;這件事目前還沒有任何 AI 能替你做。不是因為技術不夠，而是因為「承擔後果」這件事本身就不是技術問題。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;這張圖最有價值的地方，不是它告訴我們 AI 多厲害，而是它讓我們看到理論和現實之間巨大的鴻溝。&lt;/p&gt;
&lt;p&gt;與其焦慮 AI「理論上能做什麼」，不如想想自己在這個落差裡能扮演什麼角色。&lt;/p&gt;
&lt;p&gt;畢竟，藍色畫得再大，沒有人把它變成紅色，它就只是一張圖。&lt;/p&gt;
</content:encoded><category>技術觀點</category><category>AI取代</category><category>Anthropic</category><category>AI理論覆蓋率</category><category>職涯觀點</category><category>LLM</category></item><item><title>你可能不需要龍蝦：自動化跟 Agent 是兩件事</title><link>https://cablate.com/articles/blog-writing-openclaw/</link><guid isPermaLink="true">https://cablate.com/articles/blog-writing-openclaw/</guid><description>很多人把 OpenClaw 當排程工具用，但自動化和 AI Agent 是完全不同的需求。搞清楚差異，CLI 排程工具可能就夠了，不用大費周章架 Agent 框架。</description><pubDate>Tue, 24 Feb 2026 02:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近跟不少人聊到 OpenClaw，發現一個很有趣的現象：大部分人對龍蝦的需求，其實就只是「自動化」。&lt;/p&gt;
&lt;p&gt;不是即時溝通，不是多 Agent 協作，不是什麼複雜的工作流編排。就只是「我希望某件事定期自動跑完」。&lt;/p&gt;
&lt;p&gt;說白了，殺雞用牛刀。&lt;/p&gt;
&lt;h2&gt;自動化和 Agent 為什麼會搞混？&lt;/h2&gt;
&lt;p&gt;這件事我覺得不能怪使用者。&lt;/p&gt;
&lt;p&gt;過去幾個月，「AI Agent」這個詞被用得太廣了。不管是排程跑任務、自動回訊息、還是幫你寫週報，全部都被包裝成「Agent」。聽久了，大家自然會覺得「我要自動化，所以我需要一個 Agent 框架」。&lt;/p&gt;
&lt;p&gt;但「自動化」和「Agent」其實是兩件事。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;自動化的核心是「可預期」&lt;/strong&gt;：你知道輸入是什麼、流程是什麼、輸出是什麼。設定好就不用管了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Agent 的核心是「不可預期」&lt;/strong&gt;：它需要根據當下的情境做判斷，可能走 A 路線也可能走 B 路線，甚至會主動決定下一步要做什麼。&lt;/p&gt;
&lt;p&gt;如果你的需求是前者，那根本不需要後者的架構。裝一隻龍蝦來跑排程任務，就像買一台越野車來通勤上班。能開嗎？能。但你多花的時間和力氣，換來的功能你根本用不到。&lt;/p&gt;
&lt;h2&gt;CLI 排程工具就能搞定的事&lt;/h2&gt;
&lt;p&gt;如果需求是「定期跑某個重複性任務」或「讓 AI 按照固定流程完成工作」，現在 Gemini CLI 或 &lt;a href=&quot;/articles/claude-code-workflow/&quot;&gt;Claude Code&lt;/a&gt; 這類命令列工具就能搞定。&lt;/p&gt;
&lt;p&gt;做法很直覺：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;預裝好 Plugin&lt;/strong&gt; — 擴充工具能力，像是讀寫檔案、呼叫 API、操作資料庫&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;寫好提示詞規則&lt;/strong&gt; — 把你的 SOP 寫成 AI 能理解的指令，固定行為模式&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;設定工作流程規範&lt;/strong&gt; — 加上驗證機制，確保產出品質不會亂飄&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;確認下指令後它能如期完成任務，接著在作業系統設定排程定期跑就完事了。Windows 用工作排程器，Mac 和 Linux 用 cron job。&lt;/p&gt;
&lt;p&gt;這不是什麼新招。過去一年很多人就是這樣用的。&lt;/p&gt;
&lt;p&gt;而且現在這些工具的排程生態已經很成熟。Claude Code 有 cron 排程、GitHub Actions 整合，甚至有專門的 scheduler plugin，可以用自然語言設定排程時間；Gemini CLI 也有 hooks 系統可以在執行的各個節點插入自訂邏輯，加上 MCP server 擴充，基本上常見的自動化場景都覆蓋得到。&lt;/p&gt;
&lt;p&gt;工具鏈都到位了，差的只是你去設定而已。&lt;/p&gt;
&lt;h2&gt;那龍蝦適合什麼場景？&lt;/h2&gt;
&lt;p&gt;OpenClaw 的核心價值不在排程，而在「即時互動」和「動態決策」。&lt;/p&gt;
&lt;p&gt;比方說：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你在 Telegram 上跟它對話，它需要根據你說的內容即時去查資料、做判斷、回覆你&lt;/li&gt;
&lt;li&gt;一個任務有多個分支，走哪條路取決於中間步驟的結果，不是事先能寫死的&lt;/li&gt;
&lt;li&gt;使用者需要「邊跑邊介入」，而不是「設定好就放著」&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這些場景需要一個常駐的、有上下文記憶的、能即時回應的系統。龍蝦的 Heartbeat 排程器、AgentSkill 擴充、還有用通訊軟體當介面的設計，都是為了這種場景而生的。&lt;/p&gt;
&lt;p&gt;但如果只是「每天早上 9 點跑一次某某任務」，一行 cron job 能解決的事，沒必要大費周章架一整套 Agent 系統。&lt;/p&gt;
&lt;h2&gt;選工具前，先搞清楚需求&lt;/h2&gt;
&lt;p&gt;這不只是龍蝦的問題。&lt;/p&gt;
&lt;p&gt;對有些人來說，看到一個新工具火了，第一反應是「我要裝起來」，而不是「我需要它解決什麼問題」。結果花了時間裝好，才發現自己的需求用更簡單的方式就能搞定。&lt;/p&gt;
&lt;p&gt;不是說龍蝦不好。三四十萬人在用，它確實解決了一群人的問題。但如果你的問題本身不複雜，解決方案也不需要複雜。&lt;/p&gt;
&lt;p&gt;先問自己一個問題就好：&lt;strong&gt;「我的任務需要 AI 在過程中做判斷嗎？」&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要 → 龍蝦或類似的 Agent 框架值得研究&lt;/li&gt;
&lt;li&gt;不需要 → CLI 工具加排程，省時省力&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;OpenClaw 新手資源推薦&lt;/h2&gt;
&lt;p&gt;千萬別來問我，我是從頭到尾自己開發的，跟 OpenClaw 沒有關聯。&lt;/p&gt;
&lt;p&gt;但目前網路上有兩份我覺得蠻新手向的資源，推薦給有興趣的人：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;@cw.lin_ 的安裝手冊&lt;/strong&gt; — &lt;a href=&quot;https://portaly.cc/cwl/product/2ZeDjb6hkj8er65KlTTj?code=Claw&quot;&gt;OpenClaw 安裝手冊&lt;/a&gt;（輸入 Claw 邀請碼免費取得，零工程背景可執行，含常見踩雷 FAQ）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;@pan_0709 的教學影片&lt;/strong&gt; — &lt;a href=&quot;https://youtu.be/QF7xkCo236g?si=2Peht_ZIgSEfkGte&quot;&gt;YouTube 教學&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;目前盡量參考安裝的部分就好，模型串接請避免用 Antigravity 或 Claude Code 這類訂閱型的服務，會被鎖。&lt;/p&gt;
&lt;p&gt;先搞清楚自己要的是「自動化」還是「龍蝦」再動手。如果你想更深入理解龍蝦底下的 &lt;a href=&quot;/articles/openclaw-agent-fundamentals/&quot;&gt;Agent 架構到底在做什麼&lt;/a&gt;，可以看這篇。這個判斷本身不花時間，但能幫你省下不少走彎路的力氣。&lt;/p&gt;
</content:encoded><category>AI工具實戰</category><category>AI Agent</category><category>自動化</category><category>OpenClaw</category><category>Claude Code</category><category>排程</category><category>CLI 工具</category></item><item><title>Claude Code 實戰：我怎麼用 AI 寫完一整個網站</title><link>https://cablate.com/articles/claude-code-workflow/</link><guid isPermaLink="true">https://cablate.com/articles/claude-code-workflow/</guid><description>分享用 Claude Code 從零建站的真實工作流，包含 SEO 設定、設計改版、內容規劃的完整過程與踩坑經驗。</description><pubDate>Wed, 11 Feb 2026 02:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言：AI 寫 code 不是重點，工作流才是&lt;/h2&gt;
&lt;p&gt;很多人對「用 AI 寫程式」的想像停在「貼需求、拿 code、複製貼上」。&lt;/p&gt;
&lt;p&gt;但真正用 AI 做完一個完整專案後你會發現——&lt;strong&gt;寫 code 只佔 30%，剩下 70% 是決策、架構、除錯和品質校正。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;這篇文章分享我用 Claude Code 從零完成 CabLate 官網的完整過程，包含哪些地方 AI 很強、哪些地方你不能偷懶。如果你還在猶豫要不要用 &lt;a href=&quot;/articles/blog-writing-openclaw/&quot;&gt;Agent 框架還是簡單的 CLI 工具&lt;/a&gt;，建議先看那篇。&lt;/p&gt;
&lt;h2&gt;技術選型：為什麼選 Astro&lt;/h2&gt;
&lt;p&gt;在開始之前，我需要決定用什麼框架。需求很明確：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;靜態內容為主（文章、作品集）&lt;/li&gt;
&lt;li&gt;SEO 要好（SSG 優先）&lt;/li&gt;
&lt;li&gt;開發體驗要好（支援 component 框架）&lt;/li&gt;
&lt;li&gt;部署簡單（Cloudflare Pages）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最後選了 &lt;strong&gt;Astro&lt;/strong&gt;，原因：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;考量&lt;/th&gt;
&lt;th&gt;Astro&lt;/th&gt;
&lt;th&gt;Next.js&lt;/th&gt;
&lt;th&gt;Nuxt&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SSG 效能&lt;/td&gt;
&lt;td&gt;零 JS 預設&lt;/td&gt;
&lt;td&gt;需設定&lt;/td&gt;
&lt;td&gt;需設定&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;學習曲線&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;td&gt;中&lt;/td&gt;
&lt;td&gt;中&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;內容集合&lt;/td&gt;
&lt;td&gt;原生支援&lt;/td&gt;
&lt;td&gt;需套件&lt;/td&gt;
&lt;td&gt;需套件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;部署成本&lt;/td&gt;
&lt;td&gt;幾乎免費&lt;/td&gt;
&lt;td&gt;Vercel 有限制&lt;/td&gt;
&lt;td&gt;同左&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;用 Claude Code 的實際工作流&lt;/h2&gt;
&lt;h3&gt;第一步：搭骨架&lt;/h3&gt;
&lt;p&gt;我不會一開始就叫 AI 寫完整個網站。第一步是建立專案結構和基礎元件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;src/
├── components/
│   ├── common/      # 共用元件
│   └── sections/    # 頁面區塊
├── content/
│   └── articles/    # Markdown 文章
├── layouts/         # 版面配置
├── pages/           # 路由
└── styles/          # 全域樣式
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;這個結構是我決定的，不是 AI。&lt;strong&gt;架構決策是人的工作。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;第二步：逐區塊開發&lt;/h3&gt;
&lt;p&gt;每個 section 獨立開發，給 AI 明確的規格：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;元件名稱、props 介面&lt;/li&gt;
&lt;li&gt;預期的視覺效果（文字描述即可）&lt;/li&gt;
&lt;li&gt;要用的 CSS 變數和設計系統&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;重點是&lt;strong&gt;一次只做一件事&lt;/strong&gt;。不要丟一個「幫我做完首頁」的需求，而是「做 Hero section，規格如下...」。&lt;/p&gt;
&lt;h3&gt;第三步：SEO 基礎建設&lt;/h3&gt;
&lt;p&gt;這部分 AI 幫了很大的忙：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;SEO 元件&lt;/strong&gt; — 自動處理 meta tags、OG、Twitter Cards、JSON-LD&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RSS Feed&lt;/strong&gt; — 自動從文章集合生成&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sitemap&lt;/strong&gt; — Astro 套件直接搞定&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分類頁面&lt;/strong&gt; — 動態路由自動建立&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但 SEO 策略（寫什麼主題、鎖什麼關鍵字）還是要自己想。&lt;/p&gt;
&lt;h3&gt;第四步：設計改版&lt;/h3&gt;
&lt;p&gt;原本的深色遊戲風格太厚重，決定改成簡潔淺色主題。這個改動涉及：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;4 個全域 CSS 檔案重寫&lt;/li&gt;
&lt;li&gt;7 個元件的 style 區塊更新&lt;/li&gt;
&lt;li&gt;所有金色/發光/漸層特效移除&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI 在這種「大範圍但規則明確」的改動特別有效率。手動改大概要半天，AI 十分鐘搞定。&lt;/p&gt;
&lt;h2&gt;AI 很強的地方&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;重複性高的改動&lt;/strong&gt; — 改 7 個元件的配色，規則一樣，AI 不會漏&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模板生成&lt;/strong&gt; — SEO meta tags、RSS feed、schema markup&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;研究整合&lt;/strong&gt; — 分析競品 SEO 策略、整理 Google 官方文件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;除錯&lt;/strong&gt; — 看 build error、找 CSS 衝突&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;AI 做不好的地方&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;設計品味&lt;/strong&gt; — AI 會給你「正確」的設計，但不一定是「好」的設計&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;商業判斷&lt;/strong&gt; — 要寫什麼主題、鎖什麼市場，AI 只能建議&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;上下文遺失&lt;/strong&gt; — 長時間對話後 AI 會忘記之前的決策&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;過度工程&lt;/strong&gt; — AI 傾向給你「完整」的方案，但你可能只需要最簡單的&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;結論：判斷力才是真本事&lt;/h2&gt;
&lt;p&gt;工具會越來越強，但&lt;strong&gt;知道什麼時候該用什麼工具、用到什麼程度&lt;/strong&gt;——這個&lt;a href=&quot;/articles/anthropic-radar-ai-cannot-replace/&quot;&gt;判斷力不會被取代&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;用 AI 建站不是「無腦」的事。你需要：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;清楚的架構思維（告訴 AI 做什麼）&lt;/li&gt;
&lt;li&gt;品質標準（判斷 AI 的產出好不好）&lt;/li&gt;
&lt;li&gt;迭代耐心（第一次不會完美，但方向對就行）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這就是我說的「用架構思維駕馭 AI」。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;這篇文章本身也是用 Claude Code 協助完成的——但每一個觀點都是我的。&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>AI工具實戰</category><category>Claude Code</category><category>AI 協作</category><category>Astro</category><category>開發實戰</category><category>AI 建站</category><category>Cloudflare Pages</category></item></channel></rss>