<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>qrtt1's blog</title>
<link>https://notes.qrtt1.io</link>
<atom:link href="https://notes.qrtt1.io/feed.xml" rel="self" type="application/rss+xml"/>
<description>qrtt1's blog</description>
<item>
<title>你的小抄，現在可以自己跑了</title>
<link>https://notes.qrtt1.io/series/experience-embodiment/ep07.html</link>
<guid>https://notes.qrtt1.io/series/experience-embodiment/ep07.html</guid>
<description>你的小抄，現在可以自己跑了 經驗具現化 — EP07：階段性回顧 寫到這裡，回頭看了一下這幾個月做的事，發現有一個模式一直在重複。 先是碰到某個重複的流程，讓人覺得煩躁，想試著快轉這個無聊的過程。可能是部署要打一堆指令，可能是看帳單要開 Console...</description>
<content:encoded><![CDATA[<p>經驗具現化 — EP07：階段性回顧</p>
<hr />
<p>寫到這裡，回頭看了一下這幾個月做的事，發現有一個模式一直在重複。</p>
<p>先是碰到某個重複的流程，讓人覺得煩躁，想試著快轉這個無聊的過程。可能是部署要打一堆指令，可能是看帳單要開 Console 慢慢點，可能是換一台電腦就忘了昨天做到哪。然後我發揮控制慾，指揮著 AI 把事情一一處理掉。好像主宰了很多事情，但這些事情大部分都只是在浪費時間的低價值活動。開始意識到這一點的時候，認真研究了一下 Claude Code 的 skill 以及 plugin marketplace，把做法寫成 skill，讓 AI 下次可以自己來。用了一陣子，又碰到還沒整理過的「每日任務」，再跑一次同樣的循環。</p>
<p>並不是我一開始就主觀認為該這樣做。是反覆整理了幾個 skill 之後，回頭反思自己的行為，才發現原來就該這麼做。至少對我而言，這樣的整理方向是舒適的。《阻止你寫點小東西的，是那個摩擦力》裡的部署 skill，跟《花最多錢的，不是你以為的那個》裡的 billing skill，被建立出來的時間大概差了兩三週。但一樣是呼叫那個魔法般的句子，把它們整理出來。</p>
<hr />
<p>這個模式跑了七次，每次的起點不同，但目標都是一致的：我必須把時間省下來。省時間的方式有兩種，一種是它可以接近自動地執行，另一種是它執行的時候我不用等待，可以去做其他事情。很直覺地，未來某些事情可以是自動在某個時間點自己發生的，我只需要做事後回覆跟給意見就好。</p>
<p>在有這些 skill 之前，部署要記一堆指令，要自己搞定所有設定。一開始這些 skill 只在我自己的電腦上，但想想我的開發環境還挺多的，而 plugin marketplace 其實可以直接加入 private repository 使用，就算是這些私人的小工具也沒問題，不代表我必須要公開地散佈它。上架之後跨機器都能用。工具箱從只有部署，慢慢長出 standup、reviewer 這些日常工具。Cloud Run 的設定從最基本的 Service，擴展到 Job、環境變數、multi-container。帳單從靠人開 Console 看，變成 skill 自動分析，看到問題就改。</p>
<p>每一步都在降低「下次再做同樣事情」的摩擦力。不是一次到位的，是一點一點磨出來的。</p>
<hr />
<p>如果你也覺得開發的日常有許多例行公事得處理，或是一些固定的套路反覆出現在你的生活裡，你應該試試看。</p>
<p>從最小的開始，甚至連 plugin 都不需要做。像我最初只是在 <code>~/.claude</code> 裡寫想要的東西而已。你平常重複在做的事，不管是部署、檢查、清理、跑報告，那就是 skill 的候選。skill 就是一份 markdown，描述「遇到這種事該怎麼做」，不需要寫程式。寫完裝上，用了再改，不用一次規劃完。</p>
<p>《這個系列想說的事》裡提過，以前的小抄是靜態的文字，它不但不會抱怨你的偷懶，也不會幫你檢查結果有沒有奇怪的地方。而現在，在這個懶人勝利的時代，AI 會替你執行、替你檢查，並且指出潛在的問題。只要你有給它足夠的指示和提示就行了。當然還是奉勸各位，檢查的東西需要明確、可以重複會比較好，reproducible, predictable。</p>
<hr />
<p>整個系列的產出是一個 plugins repository，但它不是給別人用的產品。它是我自己的工具箱，裡面裝的是我的習慣、我的流程、我踩過的坑。</p>
<p>已經有非常多人推出好用的 plugin 放在各種 plugin marketplace 上面。我並不打算把這個私人的工具箱推出去，因為私人的東西就必須為個人的習慣而打造。我並沒有覺得自己的行為足夠通用，也不覺得值得推廣。但這正是重點：你的工具箱應該長成你自己的樣子。沒有困難的門檻，你只需要呼叫 skill-creator 就行了。試著覺察自己生活中的不便利，或是流程上的各種摩擦點，做出對應的 skill 去處理它。多數的情況都不可能一次到位，但只要它有了形體，就可以再一次改變形狀來符合你的問題。</p>
<hr />
<p>這是一個階段性的結尾。工具會繼續長，規則會繼續演化，新的摩擦會繼續出現。但現在我知道怎麼處理它們了。</p>]]></content:encoded>
<pubDate>Sun, 05 Apr 2026 00:00:00 +0000</pubDate>
</item><item>
<title>花最多錢的，不是你以為的那個</title>
<link>https://notes.qrtt1.io/series/experience-embodiment/ep06.html</link>
<guid>https://notes.qrtt1.io/series/experience-embodiment/ep06.html</guid>
<description>花最多錢的，不是你以為的那個 經驗具現化 — EP06：帳單分析自動化 《EP01：阻止你寫點小東西的，是那個摩擦力》講過我每天都看帳單。帳單本身不難看懂，用了雲端那麼多年，項目大致都認得。重點是要有意識地週期性檢查，看花費項目是否與認知有落差。 GCP Console...</description>
<content:encoded><![CDATA[<p>經驗具現化 — EP06：帳單分析自動化</p>
<hr />
<p><a href="https://notes.qrtt1.io/ep01.html">《EP01：阻止你寫點小東西的，是那個摩擦力》</a>講過我每天都看帳單。帳單本身不難看懂，用了雲端那麼多年，項目大致都認得。重點是要有意識地週期性檢查，看花費項目是否與認知有落差。</p>
<p>GCP Console 上的數字看得到，但要逐項比對哪些是預期的、哪些不該出現，不是難，是花時間，加上 billing console 的操作本身就煩瑣。舉例來說，我將 Cloud Run 的 Service 改成 request based billing，理論上沒流量就不花錢。但 Artifact Registry 呢？Cloud Storage 呢？這些是搭建服務會使用的基礎建設，我們主觀地決定讓 AI agent 採用它們，但它們的成長曲線，得在合理的範圍內。</p>
<p>有一次為了解決 Cloud Run deploy 時 container image 建立耗時的問題，做了架構調整：替專案預先建立 base image，把不常變動的 dependency 先打包好，deploy 時只需要疊上應用程式碼，速度快很多。但隔天例行檢查帳單時，出現了預期外的花費。看到 Artifact Registry 的網路傳輸費用（SKU: Network Internet Egress AsiaPacific to AsiaPacific）有明顯成長，是前幾天沒有的，就瞬間明白了。</p>
<p>流程是這樣：</p>
<pre><code>GitHub Actions             Artifact Registry
  (external)                    (GCP)
      |                          |
      |  1. build base image     |
      |  (pull from Docker Hub)  |
      |                          |
      |  2. push base image      |
      | -----------------------&gt; |
      |                          |
      - - - - - - - - - - - - - - -
      (1-2 only when base changes)
      - - - - - - - - - - - - - - -
      |                          |
      |  3. pull base image      |
      | &lt;----------------------- |
      |     (network egress)     |
      |                          |
      |  4. docker build         |
      |  (base + app code)       |
      |                          |
      |  5. push final image     |
      | -----------------------&gt; |
      |                          |
</code></pre>
<p>base image 存在 GCP 的 Artifact Registry，GitHub Actions 要 build 最終 image 時得先把 base image 拉出來，build 完再 push 回去。那個從 Artifact Registry 被拉出來的 base image 就是多花的網路傳輸費用。</p>
<p>對有經驗的雲端使用者來說，看到這個帳單大概就直接反應過來了：GitHub Actions 根本不需要去拉那個已經建好的 base image。解法很簡單，讓建立 image 的動作完全在 GCP 內部完成就行了。因此在這個時候，我們才開始啟用 Cloud Build 服務。</p>
<!-- TODO: Artifact Registry 132 個 image、23.9 GB、每週 NT$14.26 的故事，有空再補 -->

<hr />
<h2>先讓帳單變得可查</h2>
<p>GCP 有一個功能叫 <a href="https://cloud.google.com/billing/docs/how-to/export-data-bigquery-setup">Billing Export</a>，可以把帳單資料匯出到 BigQuery。照著<a href="https://cloud.google.com/billing/docs/how-to/export-data-bigquery-setup">文件</a>設定就好，找不到選項的話截圖給 AI agent 讓它協助定位。</p>
<p>開了之後要等，而且等的時間比你想的久。這個東西非常有趣：打開之後不是馬上就有資料，GCP 會從今年一月一日開始慢慢回填。我三月底開的，隔天去看只有一月到二月中的資料，三月的完全沒有。再過兩三天多一點，到三月底才慢慢看到三月的資料出現，真正看到完整的三月帳單已經是四月一、二號的事了。這種「設定完馬上去查結果發現沒資料」的落差，如果沒有日誌記錄，很容易誤以為設定錯了。</p>
<p>資料進來之後，我寫了一個 billing skill。跟 AI 說一聲，它就去 BigQuery 查帳單，不用自己開 Console。</p>
<hr />
<h2>Billing Skill 的設計</h2>
<p>這個 skill 分三層，漸進式分析：</p>
<p>第一層是 pre-check。確認 bq CLI 裝了沒、project 對不對、billing export table 存不存在。如果 table 不存在，直接輸出設定步驟，不讓你跑空查詢。</p>
<p>第二層是 quick summary，也是預設行為。跑本週每日費用，用兩種方式抓異常：統計法（mean + 2σ，資料夠多的時候）和比較法（跟上週同一天比，漲幅超過 50% 且差額超過 NT$1 才標記）。再列出近七天花費最高的 SKU 排序，讓你一眼看到錢花在哪。</p>
<p>第三層是 full analysis，要的時候才跑。三十天趨勢、各 service 費用分佈、SKU 層級排序。給你完整的成本結構。</p>
<p>整個 skill 就是純 SQL 查詢，不需要額外的 Python 套件，bq CLI 查完直接在 terminal 看結果。</p>
<p>不過，如果你還記得我們最初設計 GCP Cloud Run 部署方案時的目標：本地機器不需要有任何 gcloud 帳號登入。很明顯，目前這個 skill 直接在本地跑 bq CLI，不符合這個設計。要符合的話也很簡單，把查詢動作委託給 GitHub Actions，用 Workload Identity Federation 搭配設好權限的 service account 就可以了。</p>
<hr />
<h2>有了 Skill 後就可以彈性分析帳單了</h2>
<p>以下是三月初到四月的每週主要花費，從 BigQuery billing export 實際查詢而來：</p>
<table>
<thead>
<tr>
<th>週次</th>
<th>主要花費</th>
<th>金額（NTD）</th>
<th>備註</th>
</tr>
</thead>
<tbody>
<tr>
<td>3/2 - 3/8</td>
<td>Compute Engine PD Snapshot</td>
<td>NT$0.69</td>
<td>固定開銷</td>
</tr>
<tr>
<td></td>
<td>Cloud Storage</td>
<td>NT$0.05</td>
<td>正常</td>
</tr>
<tr>
<td>3/9 - 3/15</td>
<td>Compute Engine PD Snapshot</td>
<td>NT$0.69</td>
<td>固定開銷</td>
</tr>
<tr>
<td></td>
<td>Cloud Storage</td>
<td>NT$0.05</td>
<td>正常</td>
</tr>
<tr>
<td>3/16 - 3/22</td>
<td>Cloud Storage Class A Ops</td>
<td>NT$30.58</td>
<td>⚠️ 意外（詳見下方）</td>
</tr>
<tr>
<td></td>
<td>Cloud Storage Standard</td>
<td>NT$1.71</td>
<td></td>
</tr>
<tr>
<td></td>
<td>Cloud Run Network Egress</td>
<td>NT$0.84</td>
<td></td>
</tr>
<tr>
<td></td>
<td>Compute Engine PD Snapshot</td>
<td>NT$0.69</td>
<td>固定開銷</td>
</tr>
<tr>
<td></td>
<td>Artifact Registry Storage</td>
<td>NT$0.69</td>
<td>image 開始累積</td>
</tr>
<tr>
<td>3/23 - 3/29</td>
<td>AR Network Egress (Asia-Asia)</td>
<td>NT$13.22</td>
<td>⚠️ 呼應開頭的 egress 故事</td>
</tr>
<tr>
<td></td>
<td>Artifact Registry Storage</td>
<td>NT$11.13</td>
<td>132 個 image、23.9 GB</td>
</tr>
<tr>
<td></td>
<td>Cloud Storage Standard</td>
<td>NT$7.99</td>
<td></td>
</tr>
<tr>
<td></td>
<td>Cloud Storage Class A Ops</td>
<td>NT$2.09</td>
<td></td>
</tr>
<tr>
<td></td>
<td>Compute Engine PD Snapshot</td>
<td>NT$0.69</td>
<td>固定開銷</td>
</tr>
<tr>
<td>3/30 - 4/5</td>
<td>Gemini API (Image output)</td>
<td>NT$21.05</td>
<td>⚠️ 興趣使然的花費</td>
</tr>
<tr>
<td></td>
<td>Artifact Registry Storage</td>
<td>NT$9.97</td>
<td>✅ cleanup policy 生效，開始下降</td>
</tr>
<tr>
<td></td>
<td>Cloud Storage Standard</td>
<td>NT$7.64</td>
<td></td>
</tr>
<tr>
<td></td>
<td>Gemini API (Text)</td>
<td>NT$1.36</td>
<td></td>
</tr>
<tr>
<td></td>
<td>Cloud Storage Class A Ops</td>
<td>NT$1.28</td>
<td></td>
</tr>
<tr>
<td></td>
<td>Compute Engine PD Snapshot</td>
<td>NT$0.61</td>
<td>固定開銷</td>
</tr>
</tbody>
</table>
<p>幾個值得注意的點：</p>
<p>Cloud Run 本身的費用始終趨近零。設定 request based billing，沒流量就不花錢，整個觀察期間 Cloud Run 最高的一週也只有 NT$0.84，全部是網路傳輸。</p>
<p>表格裡標了三個意外花費，各自有不同的故事：</p>
<p>3/16 那週 Cloud Storage Class A Operations 突然飆到 NT$30.58。這是整個觀察期間單一 SKU 最高的一筆。Class A Operations 是寫入和 metadata 操作，平常每週不到一塊錢，這週暴增三十倍。原因是 media archive 專案初期不打算放資料庫，直接讓 GCS 充當資料庫暴力操作。每次使用者瀏覽網頁，就會去做各種列出素材清單、透過圖檔取得素材 metadata 的操作。Class A Operations 按次計費，瀏覽幾次頁面就累積幾次 API 呼叫，量一上來費用就跟著上來了。</p>
<p>3/23 那週的 Artifact Registry Network Egress NT$13.22，就是文章開頭說的那個故事。為了加速 deploy 引入 base image，結果 image 在 Artifact Registry 和 GitHub Actions 之間來回傳遞。發現後改用 Cloud Build，egress 就歸零了。</p>
<p>3/30 那週的 Gemini API NT$21.05 是興趣使然的花費。測試 Gemini 的圖片生成功能，image output token 的單價不低，玩了幾次就花掉二十幾塊。這類花費不是部署平台的成本，是自己好奇心的代價，知道就好。</p>
<!-- TODO: 其他改善過的例子待補充 -->

<hr />
<h2>處理帳單花費的心態</h2>
<p>上面列了好幾種帳單花費，每一筆成因都不一樣。有些是刻意的選擇，像是用 GCS 當資料庫、測試 Gemini 圖片生成。有些是刻意選擇了某種實作方式，但沒有意識到這樣會造成花費的提升，像是 base image 的 egress。不管我們有沒有主觀認為錢會這樣被使用，看到帳單之後都還有修正的機會，只要它不爆量就行了。</p>
<p>以 Artifact Registry Storage 為例，解法就很直接：設 cleanup policy。</p>
<pre><code class="language-json">{
  &quot;keep-recent-1&quot;: { &quot;action&quot;: &quot;Keep&quot;, &quot;keepCount&quot;: 3 },
  &quot;delete-old&quot;: { &quot;action&quot;: &quot;Delete&quot;, &quot;olderThan&quot;: &quot;7d&quot; }
}
</code></pre>
<p>保留最新 3 個版本，超過 7 天的自動刪除。</p>
<p>可以這樣做的原因很簡單：我們每次部署總是用最新的版本，也沒有退回舊版的需求。就算真的要退版，也是建立一個更新的版號但使用舊版的內容，不會去拉舊的 image。既然如此，多餘的東西直接刪掉就可以了。保留 3 個是給自己一點安全邊界，但老實說，大概也用不到。</p>
<p>設完 policy 之後，image 數量從 132 降到 46，之後持續自動清理。</p>
<p>再來看 GCS Operations 的改善。前面提到 media archive 專案不打算處理資料庫，直接拿 GCS 當資料庫用，結果 Class A Operations 飆到 NT$30.58。那要改善花費的時候怎麼做呢？</p>
<p>一種做法是給它一個正式的資料庫。但問題來了，如果要特意開一台機器來存，那就是另一種花費。我曾經考慮過幫它設定一個 Tailscale sidecar，連回家裡的機器直接用地端的 PostgreSQL。但想了想，我沒有任何一台固定的主機保證一定存在且不被關機。</p>
<p>換個方向想，其實只要它的操作行為不要這麼多就好了。既然是 side project，乾脆直接用最簡單的 SQLite。讓 AI agent 把原本直接操作 GCS 的邏輯改成存在 SQLite，只要有任何 update 或 delete 行為，就上傳一份 SQLite 到 GCS 上面。所以 SQLite 只是這些 GCS operations 的 buffer。</p>
<p>雖然聽起來有一點亂來，但這就是我自己的個人專案，別人也管不到我呀。</p>
<hr />
<h2>帳單 Skill 帶來的改變</h2>
<p>因為 billing skill 被建立出來，我們可以有彈性又不厭其煩地去檢查帳單問題，用不同的角度查看目前的成本合不合理。想看每週趨勢就看每週，想看 SKU 明細就看 SKU，不用每次都自己開 Console 慢慢點。</p>
<p>更重要的是，這也幫助我把「檢查帳單」這個習慣具現化成一個可以執行的功能。原本只是每天看一眼 Console 的模糊習慣，現在變成了一個有結構的分析流程，可以重複執行，也可以讓 AI agent 代勞。</p>
<hr />
<p>下一篇，來做個階段性的回顧。</p>]]></content:encoded>
<pubDate>Thu, 02 Apr 2026 00:00:00 +0000</pubDate>
</item><item>
<title>格局打開，我們要更多的 Cloud Run 設定</title>
<link>https://notes.qrtt1.io/series/experience-embodiment/ep05.html</link>
<guid>https://notes.qrtt1.io/series/experience-embodiment/ep05.html</guid>
<description>格局打開，我們要更多的 Cloud Run 設定 經驗具現化 — EP05：需求又推著 skill 往前走了 接著另一個 side project 冒出來了——tcat-qrcode-form，黑貓宅配的 QRCode 填單小工具。它是給零售業主用的，不需要電腦操作，給你一個實體的預建單號，掃...</description>
<content:encoded><![CDATA[<p>經驗具現化 — EP05：需求又推著 skill 往前走了</p>
<hr />
<p>接著另一個 side project 冒出來了——tcat-qrcode-form，黑貓宅配的 QRCode 填單小工具。它是給零售業主用的，不需要電腦操作，給你一個實體的預建單號，掃 QRCode 用手機打開網頁就可以填寫寄貨資訊，相當方便。這個專案裡得跑 Playwright 跟 QRCode scanner，需要更細緻地控制 Cloud Run 的行為——加大記憶體、調整 request timeout、設定 startup probe，這些在前一個專案都不需要操心，但這次不行。</p>
<p>問題是，mini-deployment.yaml 裡沒有地方放這些設定。Claude 問我：要直接在 deploy 的時候加 <code>gcloud</code> 的 flag，還是擴充設定檔的格式？</p>
<p>直接加 flag 的話，每次部署都要記一長串參數，而且這些參數散落在 skill 的邏輯裡，不在使用者的 repo 中，換個專案又要重新設定。擴充設定檔的話，v1 的 <code>components</code> 格式已經開始不夠用了——同一個清單裡，有的 entry 只需要一個 <code>dockerfile</code>，有的需要額外的設定欄位，格式開始分裂。</p>
<p>再加上 media archive 那邊曾經想過要加 sidecar——開一個 tailscale sidecar 連到自己的電腦，透過「內網」呼叫地端的服務。雖然最後覺得需求不夠強烈沒有進行，但光是「想描述這件事」就讓我意識到，v1 的一個 <code>dockerfile</code> 欄位根本描述不了這種組合。</p>
<hr />
<h2>Knative YAML：讓 Cloud Run 回到它的原生格式</h2>
<p>這時候過去寫 Kubernetes 服務的經驗就派上用場了。Cloud Run 底層本來就是 Knative，它接受 Knative service YAML 作為部署設定。與其自己再包一層格式去描述這些進階設定，不如直接讓使用者寫 Knative YAML，工具只負責 build image 和處理認證。resource、probe、concurrency、sidecar——全部寫在 Knative YAML 裡，一目了然。</p>
<hr />
<h2>mini-deployment.yaml v2</h2>
<p>於是我們決定做 v2。先把現有的格式正式標為 v1，然後設計新格式：<code>components</code> 拆成 <code>services</code> 和 <code>jobs</code> 兩個頂層 key，不再靠 <code>type</code> 欄位區分。需要進階設定的 service 就多一個 <code>spec</code> 欄位指向 Knative YAML，<code>build</code> 欄位列出需要 build 的 image。簡單的 service 還是一個 <code>build</code> 字串搞定。</p>
<pre><code class="language-yaml">version: 2
services:
  - name: znepwgud
    spec: knative/service.yaml
    build:
      app: ./Dockerfile
</code></pre>
<p>需要進階設定的 service 加一個 <code>spec</code> 指向 Knative YAML，裡面可以寫 resource limits、startup probe、container concurrency 這些 Cloud Run 原生支援的設定。build 裡列出要 build 的 image，名稱對應 Knative YAML 裡 container 的 <code>image</code> 欄位。</p>
<hr />
<h2>Migration：v1 到 v2 的路</h2>
<p>光定格式還不夠。既然有了 v1 和 v2，就需要 migration 的路徑。Claude 做了一個 v1-to-v2 的 converter 工具，也做了對應的 migration skill——偵測 repo 裡的設定檔版本，預覽轉換結果，確認後就地轉換。deploy skill 那邊也同步更新，讓它能同時讀懂 v1 和 v2，在記憶體裡統一轉成 v2 的結構來處理。</p>
<p>你依然可以感受到，這些規格都不是事先想好的，而是有需求才去把它做出來，並且模擬著一個 migration 的歷程。當然我可以直接叫 agent 幫我從 v2 重寫，反正現在也只累積到第二個 side project 在用。但我就是順便想一下，如果這是在工作上用的話，流程應該要是什麼樣子才會順暢，特別是當已經有很多專案用你的工具在管理的情況，更要注意這樣的演進過程。</p>
<p>需求推著格式走，格式推著工具走。每一次改版都是因為真的有一個部署需求卡住了，才回頭去改。</p>
<p>下一篇，部署的事告一段落。但部署會花錢，而花最多錢的，不一定是你以為的那個地方。</p>]]></content:encoded>
<pubDate>Sat, 28 Mar 2026 00:00:00 +0000</pubDate>
</item><item>
<title>部署之外的工具包</title>
<link>https://notes.qrtt1.io/series/experience-embodiment/ep04.html</link>
<guid>https://notes.qrtt1.io/series/experience-embodiment/ep04.html</guid>
<description>部署之外的工具包 經驗具現化 — EP04：不只是部署，還有做事的節奏 上一篇講的是部署工具怎麼被 side project...</description>
<content:encoded><![CDATA[<p>經驗具現化 — EP04：不只是部署，還有做事的節奏</p>
<hr />
<p>上一篇講的是部署工具怎麼被 side project 推著長大。但做事的過程中，我發現有一個空白需要被填補，那就是怎麼「啟動自己的大腦」。常常坐在電腦前，想試著回憶一下上次做了什麼，自己自言自語地列舉了一些項目，但是又生怕忘了什麼。我總是會要求 Claude 替我看一下 git history，順便讓他回顧一下 daily 裡面的文件記錄，總結出一份我今天要做什麼的清單。花最多時間的不是技術問題，而是「管理做事的節奏」。每天開工要回想昨天做到哪、要釐清接下來該做什麼、討論完方向之後剩下的都是比較無趣的日常。這些也是摩擦，不在技術上，在協作的流程裡。於是這個 infra 專案開始長出另一組 skill，不是拿來部署的，是拿來管理工作節奏的。</p>
<p>先說一下名字的由來。twjug-lite 是過去舉辦社群活動時開的 GCP project name，現在只是個 side project 放置區，跟活動本身沒有關係了。之後的 skill 改用縮寫版本的 <code>tl-</code> 作為 prefix。</p>
<hr />
<h2>盤點一下手上的工具</h2>
<p>GCP 相關的 skill 是從部署需求長出來的，它們也是建立目前專案的起點。不過最後要讓開發的節奏順暢合理，還是加了不少東西。特別是，當一個 side project 會在各種零碎的時間，被不同空閒時段的自己前仆後繼地改寫。</p>
<p>原本打算做的：</p>
<ul>
<li><code>twjug-lite-gcp-deploy</code> — 部署 Cloud Run Service 或 Job，支援單一 Dockerfile 和 mini-deployment.yaml 多 component 模式</li>
<li><code>twjug-lite-gcp-undeploy</code> — 移除已部署的 Service 或 Job</li>
<li><code>twjug-lite-gcp-reviewer</code> — 檢查 repo 的結構是否準備好可以部署，也能輔助全新的專案完成設定</li>
</ul>
<p>這三個是「開發」的工具。接下來要講的是原本沒打算做，但後來長出來的「管理做事節奏」的工具：</p>
<ul>
<li><code>tl-daily-standup</code> — 上工前的盤點，自動掃描 git log、日誌、設計文件，產出今天要做什麼的報告</li>
<li><code>tl-daily-i-am-lazy</code> — 委派模式，討論完方向後把剩下的事全權交給 agent 執行</li>
</ul>
<hr />
<h2>tl-daily-standup：上工前的盤點</h2>
<p>模擬每日在辦公室的 standup 風格。啟動一個新的 Claude session，叫一聲 standup，它會自動掃描這些東西：</p>
<ul>
<li><code>git log</code> — 最近的 commit 紀錄</li>
<li><code>docs/daily/</code> — agent 工作過程中寫的日誌（還記得嗎？CLAUDE.md 裡有一條「學到新東西就記日記」的規則，agent 在工作過程中學到的事都會寫進這個目錄）</li>
<li><code>docs/superpowers/specs/</code> 和 <code>docs/superpowers/plans/</code> — 設計文件和實作計劃</li>
<li>agent 記憶中的待辦事項和進行中的工作</li>
</ul>
<p>掃完之後產出一份三段式報告，長這樣：</p>
<pre><code>## 最近做了什麼
- 完成 deploy skill 的 mini-deployment.yaml 多 component 支援
- 實作 Cloud Run Job 部署流程
- 新增 reviewer skill，檢查 repo 部署準備度

## 待完成 / 規劃中
- sidecar 支援尚未實作
- mini-deployment.yaml v2 格式設計中

## 建議下一步
1. 繼續 v2 格式設計，解決 multi-container 的需求
2. 補齊 reviewer skill 對 v2 格式的檢查邏輯
</code></pre>
<p>不用自己翻紀錄，開個 terminal 叫一聲就好。同時在看完報告之後，你還可以跟它一一核對哪些項目是不是已經完成，因為有時候它的總結不完全正確。在核對的過程中也可以幫助自己回想，或是去做出不同的決定，也許某些項目已經不重要了，可以取消或延後，或者是去修正它的結論。</p>
<hr />
<h2>tl-daily-i-am-lazy：委派模式</h2>
<p>這個 skill 的需求發現，跟 brainstorming 有關。用了 brainstorming 之後，我發現它真的太細心了，大事小事都來問一遍。但老實說，我沒那麼多強烈的意見。多數時候我只想快速啟動，馬上進入 build-test-review 的循環。所以我需要一個小精靈，代替我同意那些不太重要的部分。</p>
<p>授權怎麼給？當我觸發這個 skill，就是同意它直接接手。agent 從對話上下文盤點出所有待完成的工作，列成清單，然後直接開始執行，不等你確認。盤點出的工作範圍內，它可以自由修改檔案、commit、甚至 push 到 remote。超出範圍的事，還是得遵守 CLAUDE.md 的「只查不改原則」。</p>
<p>遇到要做決定的時候怎麼辦？它不會停下來等你。它會記錄可選方案，從對話脈絡、日記、CLAUDE.md 規則和記憶中的使用者偏好來推測你的傾向，然後自己決定、繼續做。</p>
<p>完工時它會產出一份報告，說明各工作項目，還有整個歷程中它自己決定了什麼事，也會列出哪些事情我應該接手做下去的。長這樣：</p>
<pre><code>## 完成報告

### 已完成
- [1] skill 拆分為 twjug-lite-gcp 和 tl-daily 兩個 plugin
- [2] 更新 marketplace 設定和 README

### 自主決策
- 決策 1：prefix 用 tl-daily- 而非 twjug-lite-daily-（理由：slash 清單太長）

### TODO（請確認）
- [ ] 確認新的 plugin 安裝指令是否正確
- [ ] blocked：marketplace 子目錄結構需要實測驗證
</code></pre>
<p>哪些做完了、自己做了什麼判斷、哪些卡住了，一目了然。你回來只需要看這份報告，接著做後續的討論，看是要延續著實作，或是做一些 e2e 的手工驗證。這裡的手工算是 human in the loop 概念中的一環，你還是得自己驗收一下。儘管，我目前的 lazy mode 已經會自動自發開 headless mode（<code>claude -p</code>）自我驗收了。</p>
<hr />
<h2>討論歸人，執行歸機器</h2>
<p>i-am-lazy 常跟 brainstorming 一起用。brainstorming 來自 Claude Code 官方的 <a href="https://github.com/anthropics/claude-code-superpowers">superpowers</a> plugin，它的設計哲學是「任何創造性的工作都需要先經過設計」。不管看起來多簡單，都要先探索專案脈絡、一個一個問釐清需求、提出 2-3 個方案做 trade-off 比較、寫成 design spec、經過 self-review、使用者確認，然後才進入實作。典型的流程是：先用 brainstorming 把方向討論清楚、把 spec 寫出來，但它問的確認項目實在太多了，我自己大多只會回應前幾個主要的方向性問題，後面就丟給 lazy mode 去收尾。討論歸人，執行歸機器。</p>
<p>這兩組 skill 的整合，我並沒有帶入新的技術，而是結合了自己的習慣與對話記錄來驅動它們，讓結果較有機會偏向我的期望。但它們改變了我跟 agent 協作的節奏。順帶一提，這裡說的「開工」和「收工」不是真的上下班，而是啟動和結束一個 Claude session。在 standup 時的開工，或要求 Claude 寫 daily 的收工。比如你想在 auto-compact 之前先讓 agent 記下目前的進度，或是討論到一半要切換到另一個專案。開工叫一聲 standup 就能接上脈絡，收工下一句「寫日誌跟反省」讓 agent 收尾就好。</p>
<hr />
<p>下一篇，繼續撞牆。另一個 side project 冒出來，需求超出了 mini-deployment.yaml 能承載的範圍，格式得再進化一次。</p>]]></content:encoded>
<pubDate>Wed, 25 Mar 2026 00:00:00 +0000</pubDate>
</item><item>
<title>邊開火邊移動</title>
<link>https://notes.qrtt1.io/series/experience-embodiment/ep03.html</link>
<guid>https://notes.qrtt1.io/series/experience-embodiment/ep03.html</guid>
<description>邊開火邊移動 經驗具現化 — EP03：Skill 上路，然後不夠用了 我有一些過去整理的影片和學習記錄放在 AWS S3 上。每次一打開來，不忍直視，又立馬關了起來。檔名亂取、目錄結構早就對不上當初的邏輯、有些東西重複了好幾份。我已經毫無心力去整理它了。也許你會說，叫 AI...</description>
<content:encoded><![CDATA[<p>經驗具現化 — EP03：Skill 上路，然後不夠用了</p>
<hr />
<p>我有一些過去整理的影片和學習記錄放在 AWS S3 上。每次一打開來，不忍直視，又立馬關了起來。檔名亂取、目錄結構早就對不上當初的邏輯、有些東西重複了好幾份。我已經毫無心力去整理它了。也許你會說，叫 AI 整理吧？！我怎麼可以放棄用 AI 寫寫小廢專案的機會呢！人類果真是不會好好遵守規則的（謎之音：只有你，就是你！不要把其他人拖下水啊！）。</p>
<p>嗯，還是先關起來，眼不見為淨。我們在新的地方開始吧，希望這次能有好一點的都市計劃。</p>
<hr />
<h2>技術選型：老熟人優先</h2>
<p>家裡沒有 Mac mini 或什麼小伺服器可以跑這種東西。所以我過去做 side project 都是選 serverless 架構。不用管機器、不用管 OS 更新、能自動關閉資源，沒煩惱的存在。簡單定義一下「老熟人」，都有我的信用卡綁定了。以不新綁卡為原則。</p>
<p>AWS Lambda 是老朋友了。作為成熟的投資者兼開發者，自動化更新自己的持股與成本是必要的。靠永豐金證券的 <a href="https://sinotrade.github.io/">SinoTrade API</a>，放在 AWS Lambda 上面加 CloudWatch Events（在我寫作時查手冊才發現，它改名為 <a href="https://docs.aws.amazon.com/lambda/latest/dg/with-eventbridge-scheduler.html">EventBridge</a> 了）排程跑的。這個機制穩穩地跑了好多年，穩定到常讓人忘了它的存在。只有壞掉時才會想起來。忘了更新券商的 Python package 所以不能動了，或是 API token 到期而無法工作。</p>
<p>當然我知道現在 vibe coding 很紅，不是說這些極高便利性的 serverless 平台跟著一起冒出來，而是它們也試著去抓住 vibing programmer 的注意，用免費額度吸引他們使用。有些開發者會追逐各家平台的免費額度，反正 vibing 都不用自己動手改，搬家的成本看起來很低，所以索性這麼做了。但我不想逐免費額度而居。通常不是平台改方案，而是額度用完了，搬去其他還有免費的地方。可是時間成本才是最大的成本，這些不必要的摩擦，我不想承受。</p>
<p>三大雲也有免費額度，但我一點都不在意，我只關心我對平台熟不熟、對成本結構是否能明確掌握。</p>
<p>Cloudflare 是一個強而有力的候選。它的 Workers 生態很成熟，edge computing 的方向也很對。但我的部署習慣是依賴 container，而 Cloudflare 的 container 服務去年中才開始 public beta，差不多接近秋天才比較像認真的產品線。對我來說還是太新了一點。</p>
<p>所以，配合剛做好的 deploy skill，這次選了 GCP Cloud Run。未來如果要支援其他平台，skill 再做 AWS 或 Cloudflare 的版本就好。反正 skill 的本質就是 SOP，換個平台就是換一份 SOP。</p>
<hr />
<h2>談談 Skill 的首戰 Side Project：Media Archive</h2>
<p>我需要一個新的地方來收集影片、素材。不是什麼公開的影音平台，就是一個私人的 media archive。做筆記的時候錄的東西、流程記錄、各種學習素材。這些東西只需要保持在我自己的 Object Storage 桶裡就好。</p>
<p>需求就是：一組 API 能接受 request，幫我把指定的影片、素材收集回來，存到 Media Archive。有需要的時候，有個簡單的網頁可以瀏覽。平時沒用到不花錢，Cloud Run 的 min-instances=0 在<a href="https://notes.qrtt1.io/ep01.html">《EP01：阻止你寫點小東西的，是那個摩擦力》</a>就設好了。Object Storage 儲存成本是已知的，並在可以接受的範圍內。</p>
<hr />
<h2>由實戰推進需求：到了該支援多 Component 部署的時候了</h2>
<p>第一步很簡單。一個 Cloud Run Service，跑一個 HTTP API，接受 request。project root 放一個 Dockerfile，跟上一篇帶著 agent 走一遍部署流程時的 hello world 一樣的起手式，只是這次是真的要用的東西。</p>
<p>上一篇做好的 deploy skill 跑一次，部署上去，拿到 URL，確認能動。沒問題。</p>
<p>到這裡為止，都在 skill 能負擔的範圍內。接著事情就開始變了。</p>
<p>這個 API 需要支援影片的下載，下載完存到 Object Storage。問題來了：Cloud Run 的 HTTP service 每次 request 有時間限制。你沒辦法在一個 request 裡面下載完所有必要的素材，有些影片很大，有些需要分段下載再合併。</p>
<p>這時候就必須要有一個能在背景做這件事的服務。Cloud Run Job 就是為了這個：你觸發它，它跑完就結束，不需要一直掛著等 request。</p>
<p>整個流程大概長這樣：</p>
<pre><code>User              API Service             Cloud Run Job          Object Storage
  |                   |                        |                        |
  |-- submit --------&gt;|                        |                        |
  |                   |-- parse &amp; store ------&gt;|                        |
  |                   |   metadata to DB       |                        |
  |                   |                        |                        |
  |-- trigger -------&gt;|                        |                        |
  |   download        |-- split into batches   |                        |
  |                   |                        |                        |
  |                   |-- dispatch batch 1 ---&gt;|                        |
  |                   |-- dispatch batch 2 ---&gt;|   (concurrent)         |
  |                   |-- dispatch batch N ---&gt;|                        |
  |                   |                        |                        |
  |                   |                        |-- download &amp; verify --&gt;|
  |                   |                        |                        |
  |                   |&lt;-- report done --------|                        |
  |                   |                        |                        |
  |-- browse --------&gt;|-- signed URL ---------------------------------&gt;|
</code></pre>
<p>API Service 負責接收請求和調度。它把大量的下載工作切成批次，每批派一個 Cloud Run Job execution 去跑。多個 Job 同時執行，各自下載完後回報 API。需要瀏覽的時候，API 產生臨時的存取連結（pre-signed URL），讓你直接從 Object Storage 讀取。</p>
<p>這個模式讓你可以在短時間內把巨大的檔案完成保存。一個 Job 下載一批，三十個 Job 同時跑，就是三十倍的下載速度。API 不需要自己做下載，它只負責調度和追蹤進度。</p>
<p>這樣的需求聽下來，我們開始遇到了原本部署工具所不支援的型態。現在一個 repo 裡有兩個東西要部署了。Service 負責接 request 和調度，Job 負責實際下載。原本的 skill 只會處理 project root 的一個 Dockerfile，還無法支援新的部署需求。</p>
<p>那陣子我總是開著兩組 Claude。一個在 twjug-lite-infra 改 skill，另一個在做 side project 本身。兩邊互相驅動，side project 碰到 skill 不夠用的地方，就切到另一邊去改。這就是「邊開火邊移動」的真實節奏。</p>
<p>這裡順便講一下「改 skill」到底是什麼意思。Claude Code 的 skill 是透過 plugin 發佈的，plugin 有版號，安裝後就是你 terminal 裡可以直接用的能力。所以「改 skill」不只是改一個 markdown 檔案，而是一個完整的循環：</p>
<pre><code>Developer          Claude Code (infra)       Marketplace        Local Plugin
    |                     |                       |                   |
    |-- modify SKILL.md -&gt;|                       |                   |
    |                     |-- validate format     |                   |
    |                     |   (skill-creator)     |                   |
    |                     |                       |                   |
    |-- &quot;release it&quot; ----&gt;|                       |                   |
    |                     |-- bump version        |                   |
    |                     |   (plugin.json)       |                   |
    |                     |                       |                   |
    |                     |-- git push ----------&gt;|                   |
    |                     |                       |                   |
    |                     |-- marketplace update -&gt;|                  |
    |                     |                       |                   |
    |                     |-- plugin update -----------------------&gt;  |
    |                     |                       |                   |
    |                     |&lt;-- done               |                   |
</code></pre>
<p>修改 SKILL.md 的內容，用 skill-creator 這個 skill 來確保格式對、觸發條件對、frontmatter 沒漏。改完之後更新 plugin.json 的版號：加 skill 或拿掉 skill 就升 minor，改現有 skill 的行為就升 patch。然後 push 到 remote，跑 <code>claude plugin marketplace update</code> 把新版推上 marketplace，再跑 <code>claude plugin update</code> 把本機的 plugin 更新到最新版。</p>
<p>聽起來步驟很多，但其實就是 modify → bump version → push → publish → update。而且這些步驟本身也可以寫成 SOP。事實上 CLAUDE.md 裡就有這麼一段：</p>
<blockquote>
<p>當使用者說「release」「release it」「發布」時，指的就是以下流程：</p>
<ol>
<li>更新 plugin.json 版號（新增 skill 升 minor，改行為升 patch）</li>
<li>push 到 remote</li>
<li><code>claude plugin marketplace update</code> 推上 marketplace</li>
<li><code>claude plugin update</code> 更新本機</li>
</ol>
</blockquote>
<p>寫在 CLAUDE.md 裡，agent 就知道了。所以在 skill 那邊的 Claude，我說一聲「release it」，它就會自己把這整串跑完。</p>
<p>兩個戰場，各自有各自的 agent 在跑。我在中間切來切去，像個傳話的人。</p>
<hr />
<h2>隨需求演化而來的 Configuration File</h2>
<p>一個 repo 要部署多個東西，那就需要一個地方描述「這個 repo 裡有什麼、各自怎麼部署」。</p>
<p>於是長出了 mini-deployment.yaml。第一版很簡單：</p>
<pre><code class="language-yaml">components:
  - name: hello-run-api
    type: service
    dockerfile: api/Dockerfile
    enabled: true
  - name: segment-downloader
    type: job
    dockerfile: segment-downloader/Dockerfile
    enabled: true
</code></pre>
<p>一個清單，每個 component 有名字、類型（service 或 job）、Dockerfile 的位置、以及一個 <code>enabled</code> 開關讓你不用刪設定就能暫停某個 component。</p>
<p>deploy skill 讀到這個檔案，就平行觸發對應的 workflow。多個 component 同時部署，不需要排隊等。</p>
<p>夠用了。暫時。</p>
<hr />
<h2>邊跑邊改的節奏</h2>
<p>回頭看這段過程，最有意思的不是 mini-deployment.yaml 最後長什麼樣子，而是它怎麼長出來的。</p>
<p>我沒有事先規劃「我需要一個多服務部署的格式」。我只是在做 side project，做到不夠用了，才回頭改工具。改完繼續做，做到又不夠用了，再改。</p>
<p>這跟<a href="https://notes.qrtt1.io/ep02.html">《EP02：懶人想要被 Carry》</a>講的起手式是一樣的邏輯：先跑通，遇到問題再改。差別在於那次是帶著 agent 走一遍就完事了，這次是帶著它走了好幾遍，每一遍都撞到一個新的牆，然後一起把牆拆掉。</p>
<p>在這過程中，新版的 deploy skill 也在這些來回碰撞裡整理出來了。skill 的 release process 也跟著成形，包含 review、bump version、push、publish 到 marketplace。這些步驟本身也變成了可以一句「release it」就跑完的 SOP。工具在長大，管理工具的流程也在長大。</p>
<hr />
<p>到這裡為止，這個 infra 專案已經不單純是 GCP deploy 工具了。部署的 skill 在長大，管理 skill 的流程也在長大。但做事的過程中，我發現自己花了不少時間在「管理做事的節奏」上，每天開工回想進度、釐清方向、討論完之後剩下的執行。這些也是一種摩擦，只是不在技術上，而在協作的流程裡。</p>
<p>下一篇，來聊這些「不是部署但也很煩」的事，怎麼被具現化成另一組 skill。</p>]]></content:encoded>
<pubDate>Fri, 20 Mar 2026 00:00:00 +0000</pubDate>
</item><item>
<title>懶人想要被 Carry</title>
<link>https://notes.qrtt1.io/series/experience-embodiment/ep02.html</link>
<guid>https://notes.qrtt1.io/series/experience-embodiment/ep02.html</guid>
<description>懶人想要被 Carry 經驗具現化 — EP02：第一次成功部署 無論有沒有使用 AI Agent 輔助 Coding，我有個自己做事的慣性。先弄一個最小的東西出來，跑通整條路，確認大脈絡可行，再回頭雕細節。有些人叫這個 walking skeleton。什麼功能都可以沒有，但用到的 tech...</description>
<content:encoded><![CDATA[<p>經驗具現化 — EP02：第一次成功部署</p>
<hr />
<p>無論有沒有使用 AI Agent 輔助 Coding，我有個自己做事的慣性。先弄一個最小的東西出來，跑通整條路，確認大脈絡可行，再回頭雕細節。有些人叫這個 walking skeleton。什麼功能都可以沒有，但用到的 tech stack 都要能貫通，確認 infrastructure 是通的。</p>
<p>當然，這招是在規劃大方向的時候好用。如果有明確會卡住你的問題，比如某個 API 根本不支援你想做的事，那是另一回事，得先把那個 gating issue 搞定。那跟流程順不順暢無關，是流程中必然會卡住的問題。</p>
<p>但這個專案面對的不是那種問題。這裡要處理的是 Developer Experience（DX，開發體驗）能不能融入 agentic 的協作方式。所有的工具都是已知的：gcloud、Cloud Run 的 service 和 job、gh CLI、GitHub Actions、Workload Identity Federation。比較不熟的大概就是 Claude 的 plugin marketplace 和 plugin 的目錄結構，但也不是什麼會卡死的東西。問題不在個別工具，而是它們得組合起來。</p>
<p>這篇就是在講這件事。我知道怎麼部署，我只是懶得做。所以我帶著 agent 走一遍，然後叫它把剛才做的事整理成 skill。下次連帶都不用帶，它自己就會了。</p>
<hr />
<h2>帶著 agent 走一遍</h2>
<p>叫 Claude 幫我生一個 FastAPI 的 hello world。真的就是一個 <code>GET /</code> 回傳一行字的那種。</p>
<pre><code class="language-python">from fastapi import FastAPI

app = FastAPI()

@app.get(&quot;/&quot;)
def root():
    return {&quot;message&quot;: &quot;hello world&quot;}
</code></pre>
<p>就這樣。沒有 Dockerfile，沒有部署設定，沒有任何跟 Cloud Run 相關的東西。</p>
<p>為什麼從這麼空的起點開始？因為我想確認的不是「這個 app 能不能跑」，而是「從零到部署上線的這條路通不通」。app 本身越簡單越好，這樣如果中間出了問題，我知道一定是部署流程的問題，不是 app 的問題。</p>
<p>接著讓 Claude 幫我補 Dockerfile。第一版很單純，就是 project root 有個 Dockerfile，沒有什麼多 service 多 job 的事。我也沒跟它交代太多細節，它也沒問我太多，雙方都照直覺在走，有問題再互相提出。</p>
<p>Dockerfile 加好了，跑部署，等了兩三分鐘，拿到一個 URL。瀏覽器打開，<code>{"message": "hello world"}</code>。</p>
<p>確認能動，馬上拆掉。換個 service name，再跑一次。也成功了。好，這條路是通的。</p>
<p>整個過程我做了什麼？老實說沒做什麼。這不是工作，是個人的娛樂，我連 permission 都開成 skip mode 讓它順順地跑下去。Superpowers 的 brainstorming 倒是問了不少問題，我認真回了前幾組之後就有點懶了，畢竟這不是什麼需要嚴肅看待的東西，後面就比較敷衍它了。我本來就知道這些步驟，寫 Dockerfile、跑 deploy、驗證、undeploy。我只是對於要親自做完這些事，有一點心理的障礙。手手說他累了，手手說他想打電動。</p>
<p>這種「知道怎麼做但就是不想動」的情境累積了幾次之後，我在這個專案裡發展出了一個叫 lazy mode 的 skill。你跟 agent 討論完方向，觸發這個 skill，它就會自己盤點對話裡的待辦事項，然後一項一項做下去。觸發就是授權，不用再一個一個確認，它可以改檔案、commit、甚至 push 到 remote。</p>
<p>有意思的是遇到需要判斷的事情它怎麼辦。它會去翻對話脈絡、翻專案日記、翻 CLAUDE.md 裡的規則，推測你大概會怎麼選，然後自己決定。做完之後給你一份報告，裡面會列它做了哪些自主決策、為什麼這樣選。你回來看一眼，不對再改就好。</p>
<p>但這是後來的事了。</p>
<hr />
<h2>神奇的咒語：把剛剛的歷程整理成 Skill</h2>
<p>在 Claude 的協助下，完成了一輪有點勞累的 micro management 式的 agent 協作。我們一起 deploy 了，也一起 undeploy 了。每完成一個動作，我就唸出那個神奇的咒語：</p>
<p>把我們剛才做的事情，整理成一個 skill。</p>
<p>這句話就是整篇文章的重點。我不只是叫 agent 幫我跑一次部署流程，我是在教它怎麼做這件事，然後讓它把學到的東西打包成可以重複使用的能力。腦袋裡模糊的經驗有了固定的形體，才能被反覆執行、被檢驗、被打磨。</p>
<p>有點像工廠在訓練機器手臂。你不會一開始就寫一堆程式去控制每個關節的角度。你先用人手帶著它走一遍動作，讓它記錄下來，然後再去微調。我做的事就是帶著 agent 走了一遍從零到部署的流程，然後說：「記住了嗎？整理成 skill，下次你自己來。」</p>
<p>於是就有了 <code>twjug-lite-gcp-deploy</code> 和 <code>twjug-lite-gcp-undeploy</code> 這兩個 skill。還有 <code>twjug-lite-gcp-reviewer</code>，它會看你的 repo 結構，告訴你缺什麼設定。這些 skill 不是我坐下來從頭設計的，它們是從實際跑過一遍的經驗裡長出來的。</p>
<p>下次有新的 repo 要部署，我不用再帶著 agent 走一遍。我只要說「幫我部署這個」，它就知道該做什麼了。</p>
<p>老實說，在這之前我對 skill 這個概念是沒什麼感覺的。文件上寫得很清楚，skill 就是一份 markdown，裡面描述 agent 該怎麼做某件事。但「知道它是什麼」跟「覺得它跟我有關」是兩回事。直到後來看了李宏毅老師的<a href="https://www.youtube.com/watch?v=2rcJdFuNbZQ">《解剖小龍蝦 — 以 OpenClaw 為例介紹 AI Agent 的運作原理》</a>，他用一句話把 skill 講通了：它就是 SOP，你常做的事情就寫成 SOP。</p>
<p>這句話點醒了我。我每次部署都在重複差不多的步驟：確認 Dockerfile、跑 deploy、驗證、undeploy。這不就是 SOP 嗎？差別只是以前 SOP 是寫給人看的，現在是寫給 agent 看的。而且 agent 不只是照著做，它還能根據當下的情境去調整細節。</p>
<hr />
<h2>懶人的起手式</h2>
<p>回頭看這一趟做的事：帶著 agent 走一遍流程 → 驗證流程可行 → 把流程打包成 skill。</p>
<p>這個模式後來變成我做所有事情的起手式。不管是部署、測試、還是後來做的各種工具，都是先手動帶一遍，確認大方向沒問題，然後讓 agent 自己把它變成可重複的能力。</p>
<p>有些嚴謹自律的開發者，習慣從 spec 和架構圖出發，那些文件會給他們比較多的安全感。我自己是偏懶的那種，如果碰到的是老熟人等級的問題，心裡大概有個底了，就不打算特地去建立這些文件。除非遇到不熟的細節，或者想讓 AI 教我點新東西，才會攤開來好好整理。</p>
<p>倒是 edge case 這件事，AI agent 幫了我很多。探索測試的書都會提到，QA 特別精於找出那些開發者因為各種原因沒有做足的角落。我以前也是那個「各種原因」的開發者。有了 agent 之後，我可以在帶它走流程的過程中順便交代：「這邊要注意什麼」「那個情境也試一下」。它不會忘記，也不會煩到對我生氣。但老實說，如果換成是我被人這樣要求，「這個也檢查一下」「那個情境再跑一次」，大概早就不耐煩了。</p>
<p>不耐煩的人類跟耐煩的 AI agent 協作，會讓生產力爆發嗎？以我自己的工作場域來說，其實不見得。比較接近現實的說法是：過去耐不住煩而沒做滿的部分，現在可以用比以前省時省力的方式做出來。細節確認做足了，品質從不及格調到可以接受，甚至高於預期。這不是爆發，是把本來就該做到的事情做到了。</p>
<p>被省下來的，其實是打字的重複勞動。核心的部分，反覆的需求討論、解法方案的評估，反而因為 AI agent 帶來的便利而多做了不少。以前覺得「差不多可以了」就收手的地方，現在會多想一輪、多試一種做法，因為試的成本變低了。</p>
<p>當然，前提是主導的人有品位、有概念。知道什麼叫「做足」，知道哪些角落值得多花時間。沒有想法的人，控制不了 AI agent，也駕馭不了細節。工具再好，方向盤還是在人手上。</p>
<p>下一篇，我們用這套新流程來實作新的 side project，在過程中繼續「碰撞」出需要追加的功能。一個 repo 只部署一個東西，夠用嗎？當然不夠。mini-deployment.yaml 的故事，就是從這裡開始的。</p>
<hr />
<h2>附註：設定的意義，是授權</h2>
<p>在<a href="https://notes.qrtt1.io/ep01.html">《EP01：阻止你寫點小東西的，是那個摩擦力》</a>和這篇之間，有一段設定的準備工作。gcloud CLI 的授權我自己會做，這不是什麼難事。比較費工的是 GitHub repo 上的 Workload Identity Federation 設定，還有 GCP IAM 那邊對應的權限配置。</p>
<p>GCP 官方文件當然都有寫，但好不好理解是另一回事。大體上的原理是透過 OIDC 和 OAuth2 互通。GitHub Actions 發出一個 token 說「我是某個 repo 的某個 workflow」，GCP 那邊驗證這個 identity 的代表性，確認沒問題就發一個臨時的 token 回來。整個過程沒有長期存在的 key file，token 用完就過期。</p>
<p>會選 Workload Identity Federation 而不是經典的 service account JSON key file，就是為了這件事：token rotation 自動化。不用自己管 key 的生命週期，不用擔心 key 外洩了要去哪裡 revoke。每次 CI 跑的時候現場換一張臨時票，用完即丟。</p>
<p>這些設定有一部分可以透過 <code>gh</code> CLI 或 <code>gcloud</code> 指令完成，Claude 也確實能幫忙指揮。但有些東西，特別是你想保密的，最好讓 AI 沒機會看到。secret 的值不需要經過對話，自己去 GitHub UI 或用 <code>gh secret set</code> 貼上就好。</p>
<p>我原本想說的是：有一部分的設定苦勞是免不了的，這是事實。但寫到這裡才意識到，這些設定動作的背後其實是同一件事：授權。不是你在 Claude Code 裡按同意讓它執行下一步的那種授權，而是你真的開了權限，讓 agent 能去操控你所擁有的資源。GitHub repo 的 secret、GCP 的 IAM binding、Workload Identity Federation 的 trust relationship，每一項設定都是你在說：「這個 identity，我信任它，它可以代替我做事。」</p>
<p>這才是設定這件事真正在做的事情。</p>]]></content:encoded>
<pubDate>Tue, 17 Mar 2026 00:00:00 +0000</pubDate>
</item><item>
<title>阻止你寫點小東西的，是那個摩擦力</title>
<link>https://notes.qrtt1.io/series/experience-embodiment/ep01.html</link>
<guid>https://notes.qrtt1.io/series/experience-embodiment/ep01.html</guid>
<description>阻止你寫點小東西的，是那個摩擦力 經驗具現化 — EP01：懶人需要一個部署方案 我常常有一些新的想法想試。但一想到試完之後要把它部署出去，就覺得好麻煩。 我手上有一個小專案，FastAPI 寫的，功能很簡單，就是想丟到一個公開的 URL 讓人試玩。Dockerfile...</description>
<content:encoded><![CDATA[<p>經驗具現化 — EP01：懶人需要一個部署方案</p>
<hr />
<p>我常常有一些新的想法想試。但一想到試完之後要把它部署出去，就覺得好麻煩。</p>
<p>我手上有一個小專案，FastAPI 寫的，功能很簡單，就是想丟到一個公開的 URL 讓人試玩。Dockerfile 早就寫好了，本機跑得好好的。接下來呢？</p>
<p>開 GCP Console，建 project。開 Artifact Registry。設 IAM。裝 gcloud CLI。跑 <code>gcloud auth configure-docker</code>。Build image，push image。然後 <code>gcloud run deploy</code>，參數一串，打錯一個 flag 就要重來，然後你又得再研究一下，又被摩擦了一次。</p>
<p>全部搞完，兩三個小時過去了。程式本身可能只花了一個下午，但部署的前置作業比寫程式還久。到底要怎麼辦呢？</p>
<p>就算有了 AI agent，還是需要克服那個「好麻煩啊」的念頭。克服的方法就是不給自己時間想，讓它自動做起來，連猶豫的機會都沒有。</p>
<hr />
<h2>先看結果</h2>
<p>最後做出來的東西長這樣。你有一個 repo，裡面有 Dockerfile，然後在 terminal 打：</p>
<pre><code class="language-bash">gh workflow run deploy.yml \
  -f type=service \
  -f name=my-demo \
  -f repo_url=https://github.com/user/my-app
</code></pre>
<p>等 GitHub Actions 跑完，大概兩三分鐘，你會拿到一個 Cloud Run 的 URL。瀏覽器打開，東西在上面。</p>
<p>本機只需要 <code>gh</code> CLI，不需要 <code>gcloud</code>，不需要任何 GCP 金鑰。這點對我來說特別重要，身上揹了太多 GCP account，不同角色不同用途，想分清楚又怕誤用。大半的時間，啟用中的都只是一個無關緊要的帳號。如果部署還要我切帳號、選 project，那個摩擦力又來了。</p>
<p>但剛開始不是這樣的。讓我說這個東西是怎麼長出來的。</p>
<hr />
<h2>三角信任：為什麼開發者不需要碰 GCP</h2>
<p>整個架構有三個角色，各做各的事：</p>
<p>你的電腦只負責「按按鈕」。透過 <code>gh workflow run</code> 送一個 HTTP 請求給 GitHub，附帶幾個參數：要部署哪個 repo、叫什麼名字、什麼 branch。就這樣，你的電腦做的事到這裡結束。</p>
<p>GitHub Actions 做所有苦工。收到請求後，它 clone 你的 repo、build Docker image、push 到 GCP 的 Artifact Registry、然後部署到 Cloud Run。所有跟 GCP 打交道的事都在這裡發生。</p>
<p>但問題來了：GitHub Actions 要操作 GCP，就需要認證。最直覺的做法是產一把 Service Account key，存到 GitHub Secrets。這樣做能動，但那把 key 是長期的，會過期、會外洩、要輪替。對一個 PoC 平台來說夠用，但總覺得可以更乾淨。</p>
<p>Workload Identity Federation 是更優雅的做法。原理是這樣的：GitHub Actions 跑的時候，GitHub 簽一個 OIDC token 說「我是 <code>twjug-lite-infra</code> 這個 repo 的 workflow」。GCP 收到後驗證 token，確認來源可信，就發一個短期 access token 回去。這個 token 用完就過期，不需要存放，不需要輪替。</p>
<p>沒有長期金鑰。金鑰不存在，就不會洩漏。</p>
<pre><code>你的電腦                  GitHub Actions                GCP
   |                           |                         |
   |-- gh workflow run -------&gt;|                         |
   |                           |-- OIDC token ----------&gt;|
   |                           |&lt;-- 短期 access token ---|
   |                           |                         |
   |                           |-- clone, build, push --&gt;|
   |                           |-- gcloud run deploy ---&gt;|
   |                           |   → Service URL          |
</code></pre>
<p>說穿了，我不想同時當開發又當 DevOps。寫功能的時候就專心寫功能，不要一邊還在想權限怎麼設、image 推到哪。這個架構做的事情就是把維運那一半的參與降到最低。開發者只管寫程式和 Dockerfile，GCP 權限集中在一個地方管理，部署操作透過 CI 代理執行。你需要的只有三樣東西：一個有 Dockerfile 的 repo、<code>gh</code> CLI 已登入、對 infra repo 有觸發 workflow 的權限。</p>
<hr />
<h2>專案初期：先做有的沒的</h2>
<p>這個專案一開始就是跟 Claude Code 一起做的。但初期做的事可能跟你想像的不一樣，不是叫它寫 deploy script。這就是放假沒事、想克服懶散的 side project，所以當然不會一開始就積極地朝目標衝。先做點有的沒的，看看網路上講的 LLM 或 Claude 的 best practice，然後就開始東摸西摸了。</p>
<p>起手第一件事是寫 CLAUDE.md。這是 Claude Code 的專案規則檔，你可以在裡面告訴它在別的地方學不到的事，像是你的偏好、你的地雷、這個專案的脈絡。我忘了在哪看到的，有人說 CLAUDE.md 最有價值的部分是那些「只有你知道」的東西，而且它應該跟著你一起演化，不是寫一次就放著。所以我從另一個專案借了幾條規則：沒有明確指示時只查不改、學到新東西就記日記。日記這件事後來變得很重要，它讓我跟 AI 可以一起反省過去、改善未來。</p>
<p>然後我想搞懂 Workload Identity Federation（WIF）到底怎麼運作。官方文件不好讀，那就叫 AI 寫個極簡版本，看不懂就一直逼它再簡化。來回幾次之後生出了一份 how-it-works.md，裡面有循序圖和說明文字。我本來就偏好這種圖文對照的格式，結果 AI 自己就往這個方向生了，我可沒特別跟它提過。</p>
<p>因為是閒暇之餘的 side project，做到一半常常會順便試一些之前想試但沒認真弄的東西。那陣子在想怎麼整理過去散落在 <code>~/.claude</code> 裡的 slash command 和 skill，想了一下，都已經在用 superpowers 了，應該讓 Claude 好好模仿它的格式，幫我整理出自己的 plugin。於是就把 skill 從手動打包改成 marketplace plugin 格式，安裝變成一行指令。所以這個專案裡面不只有 GitHub Actions 執行的各種 gcloud wrapper 來包裝迷你專案的部署，還包含了 Claude plugin 裡的 skill，讓你在 terminal 裡就能直接操作 Cloud Run 的 deploy 和 undeploy。</p>
<p>回到部署工具本身。一開始只做了 service 的支援，所有 script 都圍繞著 service 在轉。一直到覺得有必要讓它能 dispatch job 出來，才開始建 job 的流程。</p>
<p>跑整合測試的時候，出了一個有意思的 bug。我請 AI 用 undeploy skill 刪除一個 Job，它卻去呼叫了 Service 版本的 <code>undeploy.sh</code>，結果當然失敗。</p>
<p>原因不是 AI 判斷錯誤，是我的 script 命名不一致。因為最早只有 service，undeploy 那邊就叫 <code>undeploy.sh</code>，沒有加 <code>-service</code> 後綴。後來加了 job 支援，deploy 那邊很對稱地叫 <code>deploy-service.sh</code> 和 <code>deploy-job.sh</code>，但 undeploy 忘了改。一部分的 script 有 service suffix，一部分只有單純的 action verb，AI 在這之間不知道該呼叫哪一個，最後選了錯的。</p>
<p>修復方式很簡單：把 <code>undeploy.sh</code> 改名為 <code>undeploy-service.sh</code>，讓 deploy 和 undeploy 兩邊都有一致的 <code>-service</code>、<code>-job</code> 後綴。改了命名，問題就消失了。不過這只是最初的版本，後來覺得讓 AI 一直寫 bash script 不是辦法，每次改邏輯都在跟 shell 的各種 edge case 搏鬥，還是叫它寫個完整的 Python script 來用比較實在。</p>
<p>跟 AI 協作，你的命名就是你的 API。如果 API 有歧義，呼叫方一定會踩到。不管呼叫方是人還是 AI。</p>
<hr />
<h2>計費安全</h2>
<p>別人做 side project 可能很在意 best practice、coding style、各種軟體工程實踐。這些對我來說都不是最重要的。我最在意的是預算控管，不想哪天收到一張非預期的帳單。所以我每天都會去看帳單，看過去兩天有沒有什麼錯誤的決策導致用量暴增，或是之前覺得可以省錢的寫法，是不是真的把費用降下來了。</p>
<p>這次做的是輔助實作 PoC 的小工具，架設需求不是長駐服務，是有人用才需要打開的。所以 Cloud Run service 可以設計成 request-based billing，只有收到 HTTP request 才計費。部署時強制帶三個參數：</p>
<ul>
<li>
<p><code>--min-instances=0</code>：沒流量時縮到零，不計費</p>
</li>
<li>
<p><code>--max-instances=1</code>：限制最大實例數，防止爆量</p>
</li>
<li>
<p><code>--cpu-throttling</code>：只在處理請求時才計算 CPU 費用</p>
</li>
</ul>
<p>沒有流量的時候，成本趨近於零。</p>
<hr />
<h2>下集預告</h2>
<p>到這邊，部署流程是跑通了，但每次都是我帶著 agent 一步一步走。它還不會自己來。</p>
<p>一個 repo 可以部署成一個 service 或一個 job。夠用了嗎？</p>
<p>我是個悠閒的單人開發團隊，且看且走，沒用到的東西就不開發，有需要再說，反正 Claude 最終會承受所有的一切。所以一直到某天真的想在同一個 repo 裡放三個 service 加一個 batch job，才發現不夠用。那時候才開始設計一個 YAML 格式來描述多容器部署，結果 Claude 幫我重寫了三次。因為有開發過 Kubernetes 服務的經驗，我知道需要類似 Pod 那樣描述多容器的東西，但不用那麼完整，也不想直接搬 Knative YAML 出來，還沒有到那麼複雜的情況。</p>
<p>至於每天看帳單這件事，後來也做了一個 billing skill，直接用 BigQuery 查 GCP export 出來的帳單資料，不用再自己開 Console 翻。能簡化的事就不要手動做。</p>]]></content:encoded>
<pubDate>Mon, 16 Mar 2026 00:00:00 +0000</pubDate>
</item><item>
<title>經驗具現化——讓工作流成為穩定的存在</title>
<link>https://notes.qrtt1.io/series/experience-embodiment/ep00.html</link>
<guid>https://notes.qrtt1.io/series/experience-embodiment/ep00.html</guid>
<description>經驗具現化——讓工作流成為穩定的存在 EP00：這個系列想說的事 記得剛成為工程師的時候，同事們有交流過怎麼記錄自己的開發 tips。其中一個人維護著一份純文字的小抄，有點長，裡面塞滿了他常用的指令、踩過的坑、某個服務的設定步驟。每次換電腦或進新專案，第一件事就是把那份小抄搬過來。 後來有了...</description>
<content:encoded><![CDATA[<p>EP00：這個系列想說的事</p>
<hr />
<p>記得剛成為工程師的時候，同事們有交流過怎麼記錄自己的開發 tips。其中一個人維護著一份純文字的小抄，有點長，裡面塞滿了他常用的指令、踩過的坑、某個服務的設定步驟。每次換電腦或進新專案，第一件事就是把那份小抄搬過來。</p>
<p>後來有了 Dropbox，那份小抄就漸漸長大了。不只是一個檔案，變成一整個資料夾。同步方便了，內容也就不客氣地膨脹起來。現在回頭看可能覺得奇怪，怎麼不用版本控制系統呢？git 那麼方便好用。因為在那時候，還不流行啊。</p>
<p>再後來，大家開始用 private GitHub repo 放自己的 dotfiles（.bashrc、.vimrc、.editorconfig 這些）、automation script、甚至打包成 Homebrew package。工作習慣搭上了版本控制和自動化，看起來比純文字進步了不少，但做的事其實一樣，就是把自己的工作經驗封裝起來，讓重複的事不用再想。</p>
<p>我在那時也有了這個概念，時不時都在優化自己的開發環境和工作經驗的記錄。這裡有一條線值得指出來：只要你能把小抄裡可自動化的部分變成 script、變成 shell profile、變成一個動作（function）、變成你自己的 setup automation，小抄的內容就可以變小，因為它漸漸成為具體可執行的檔案，或是你開發群體內的最佳實踐。開發流程也跟著變得穩定。當你累積了足夠的 credit，這些東西可以成為團隊內建立新工作流程或軟體專案的範本，需要額外做的事又會更少了。</p>
<p>只是到了現在，形式又換了一次。</p>
<hr />
<p>這兩年，隨著 agentic workflow 逐漸受到開發者喜愛，我也用 Claude Code 在做一樣的事情。不只是叫它寫程式，而是跟它一起跑部署、看帳單、管工作進度、甚至在我離開的時候把剩下的事做完。這無關於 AI agent 是否有這些能力，它需要知道的是你的期望和偏好，而這些是 LLM 的學習材料裡沒有的，因為它不是普遍的知識。是我在反覆使用的過程中，把摩擦到的地方一個一個修掉，慢慢發展出自己的 plugins。</p>
<p>封裝這些能力的方式，是 Claude Code 的 plugin 和 skill。skill 是一份 markdown，裡面寫的是「遇到這種事該怎麼做」的知識。plugin 是把一組 skill 打包起來，可以安裝、可以更新、可以跨機器同步。</p>
<p>聽起來很正式，但它就像我們過去在各種 IDE 環境裡安裝過的 plugin。只是在過去，要自己擴充 IDE plugin 的門檻稍微高了一點。對比之下，給 Claude Code 裝上 plugin marketplace，再選擇要用的 plugin 裝上，難易度明顯不同。說穿了，它就是新一代的小抄。</p>
<p>差別在於，以前的小抄要自己看仔細，自己努力把每一步做對，特別是那些得嚴肅看待的 deploy 任務。現在是 AI 替你看，幫你注意細節，你只需要 review 和 approve。但本質沒變，都是你的工作經驗，用你習慣的方式，解決你反覆碰到的問題。</p>
<hr />
<p>但光是把經驗寫下來還不夠。「經驗」這種無形的東西，寫成文字之後多少會失真。而看的人又各有各的主觀認定，可能忽略了一些前置條件，結果難以被複製。以前的小抄就有這個根本問題：你寫了，但不一定會照著做。步驟可能跳過、順序可能打亂、某個你覺得不重要的細節被省略。小抄是靜態的文字，它不會反抗你的偷懶。</p>
<p>Skill 不一樣。你把工作流寫成 skill，AI 會逐字照著跑。跑的過程中，它會碰到你沒想到的 edge case、會踩到你描述得不夠精確的地方、會在某個步驟卡住然後回報。這些摩擦不是壞事，它讓你看見自己的工作流裡有哪些是真的穩固的、哪些只是你以為穩固的。</p>
<p>當你的經驗被具現化成 skill，它就有了固定的形體。有了形體，才能被反覆執行、被檢驗、被打磨。每次碰壁都是一次回饋，每次修改都讓它更穩定一點。經過反覆的使用和打磨，你會發現摩擦越來越少。到了某個程度，你會開始有點信任感，在可以授權的範圍內，讓它獨自完成一些事情。</p>
<p>這就是「讓工作流成為穩定的存在」的意思。不是一次到位，是磨出來的。</p>
<hr />
<p>這個系列記錄的就是這樣一個過程。我的工作流怎麼從腦袋裡的模糊經驗，變成可以被執行、被檢驗、被持續改善的 skill 和 plugin。你不會看到我發佈什麼通用的 plugin，這裡只是個講故事的系列。而那些歷程與經驗，都是個人化的。</p>]]></content:encoded>
<pubDate>Sun, 15 Mar 2026 00:00:00 +0000</pubDate>
</item><item>
<title>當 ESP32 新手遇上閉環開發流程</title>
<link>https://notes.qrtt1.io/posts/esp32-closed-loop.html</link>
<guid>https://notes.qrtt1.io/posts/esp32-closed-loop.html</guid>
<description>把閉環開發的概念應用到 ESP32 開發，透過 AI 助手管理編譯、燒錄、監看的完整流程，建立可自動驗證的開發循環。</description>
<content:encoded><![CDATA[<p>儘管有 ESP32 這樣的 IoT 裝置開發經驗，但都是個人的私下娛樂休閒的小專案。在工作上以它的開發為主的經歷是沒有的。最近剛好又有個新的專案需要用到，在「這個年代」有了不同的開發方式了，特別是可以添一點 AI 味上去。</p>
<p>在前一陣子寫了一篇關於「閉環」流程的概念，它也很適合用在 ESP32 或 Arduino 相關的開發：</p>
<p><img alt="閉環流程" src="https://notes.qrtt1.io/posts/images/esp32-closed-loop.png" /></p>
<p>我們用 AI 助手管理整個開發流程，此圖以 claude code 作為案例，理論上你可以用你喜歡的那一個。隨著 esp-idf 發展越來越完整，它可以用指令模式完成所有的開發工作。</p>
<p>所以閉環的路徑會是：</p>
<ol>
<li>AI 助手啟動驗證流程，確定現在「狀態」是好的</li>
<li>開始依時做目標進行變更內容（可以是程式碼或變更參數的設定）</li>
<li>再次驗證「狀態」是好的</li>
<li>如果你有額外的 Review 程序可以接在這裡，如果有變更應該再一次驗證狀態</li>
<li>在穩定的狀態下 commit 之後回到第一步（繼續小步快跑）</li>
</ol>
<h2>該怎麼開始呢？</h2>
<p>應用閉環的概念很容易，但你要確保給予 AI 助手足夠的情境資訊。例如我們的第一步是驗證狀態的流程。那他有幾個很簡單的檢查方法，特別是對於 ESP32 來說，我們可以分別在兩個關鍵點的前後做驗證：第一個關鍵點是「編譯期」，第二個關鍵點是執行期。</p>
<p>編譯期理解，他就如同大多數需要編譯的程式語言一樣可以透過編譯器去檢查多數的靜態問題。但是邊編譯期到執行期中間還多了一個可以驗證的步驟，那就是韌體的燒錄與上傳。不過這個步驟通常你的 AI 助手看到有問題就會主動去解決，舉例來說，編譯出來的韌體太大無法燒錄，那你就會看到 Error Message 出現。當然在這個時候我們已經不會自己去看他了，都是你的 AI 助手在看訊息。真正到了執行期之後，才是可以簡單地跑起來，並且沒有預期之外的 Error Message 那就是一個好的狀態。</p>
<p>以我們今天的 esp-idf 開發工具來說他就是這樣簡單的句子而已：</p>
<pre><code class="language-bash">script -F log.txt idf.py -p /dev/cu.usbmodem1101 flash monitor
</code></pre>
<p>這是一連串的動作，但主要是燒錄後直接呼叫監看的指令讓你的 AI 助手直接接管 STDOUT / STDERR（其實他是直接看 Serial Port 的資料）。而最開頭的 script 除了讓人類有參與感可以偷看中間的過程之外，順便處理了 monitor 指令需要 TTY 的問題。</p>
<p>換句話說，你只要能夠把情境資訊塞回 AI 助手身上這個閉環大概就建立起來了。</p>
<h2>建立檢查點</h2>
<p><img alt="檢查點設計" src="https://notes.qrtt1.io/posts/images/esp32-checkpoints.png" /></p>
<p>再完成的基本的閉環建立之後，剩下的問題就是檢查點的建立。它可以是被印出來的文字資訊，也可以是額外的獨立程式交互下的結果。舉例來說 esp-idf 裡面有一個 Wifi Provisioning 的範例，他提供使用者設定 Wifi 連線資訊的功能。我們可以把這個範例套在自己的專案裡面，但是你要把它搬出來需要花一點功夫，特別是他裡面有一些精巧的設計，並不是我們一般所熟悉的寫法。</p>
<p>儘管還不知道怎麼做，但我們肯定可以先建立出閉環。但是檢查點要怎麼弄呢？如果我選擇了使用低功耗藍牙進行認證資訊的傳送，那我就可以額外要求 AI 助手呼叫傳送認證資訊的程式，並且他也期待在 Log Message 裡面看到獲得認證資訊的細節，同時連上網路之後他也可以呼叫 API 並顯示結果，上述這些都是可以用的檢查點。</p>
<p>你可以想像成在一些妙不可言的專案內，我們沒辦法像過去熟悉的軟體開發那樣建立單元測試，那他就只能夠用實際資料來進行實測，所以每一次的測試都是來真的，透過各種的訊息內容，還有實際互動完成功能的驗證。</p>
<h2>設定合理的目標</h2>
<p>這裡推薦的合理目標是你如何向 AI 助手描述你的想法。閉環開發是很方便的利器，但是他往往會因為願望許得太大了失敗。就算我們沒辦法建立單元測試，還是會期望你試著小步快跑，累積許多的 small success。</p>
<p>像最近看了不同的開發者對於使用 AI 助手的心法，裡面有一招叫做 Peel and Wrap（剝皮接枝），算是我近期最喜歡的實作策略，因為在 esp-idf 裡面有豐富的範例。但是他的範例通常太過完整，為了展示他提供的 API 多出了許多與我自己專案無關的內容。這些內容都都是可以精簡的，所以我最常做的目標就是拿著別人實作好的專案，讓 AI 助手試著把它退階為 MVP 雛形，並且強調我們是去砍掉不必要的功能，不會修改改內部的實作也不會去進行重構，盡可能保持原汁原味的方式去還原他最精簡的版本。</p>
<p>有了這一個精簡的 MVP 就有許多應用的方式。可以這個版本為例，寫實作教學（當然是讓 AI 助手去整理，不會完全自己手寫就是了）。或是以這個精簡的版本，再一次的轉化成獨立的模組，它可以是簡單的分割到不同的檔案，或是真的整理成符合 esp-idf component 規範的模組。無論如何他都可以方便我們整合到自己的場域之內。</p>]]></content:encoded>
<pubDate>Sat, 27 Sep 2025 00:00:00 +0000</pubDate>
</item><item>
<title>「凡事求個圓」- AI 助手開發的閉環實踐</title>
<link>https://notes.qrtt1.io/posts/closed-loop-development.html</link>
<guid>https://notes.qrtt1.io/posts/closed-loop-development.html</guid>
<description>為什麼我們用 AI 助手開發程式時，明明 AI 可以快速產生各種 script 和 command，但整個開發流程還是很痛苦？關鍵在於開發流程是不是閉環的。</description>
<content:encoded><![CDATA[<h2>為什麼開發流程總是卡在人工介入？</h2>
<p>最近在思考一個問題：為什麼我們用 AI 助手開發程式時，明明 AI 可以快速產生各種 script 和 command，但整個開發流程還是很痛苦？</p>
<p>我認為可能是因為開發流程不是「閉環」的。所謂閉環就是整個流程能夠自己產生反饋，不需要外部介入就能持續運轉。相反地，非閉環的流程中間有斷點，需要人工介入來「橋接」，這樣 AI 就無法獨立完成整個循環。但如果能讓所有環節形成閉環，AI 就能真正自動化開發了。</p>
<h2>非閉環的痛苦：人類成為開發瓶頸</h2>
<p>我主要在開發日常維運應用的工具，在 Windows 環境上執行維運需要的指令。大部分是撰寫 PowerShell script，不管是取得裝置的狀態、查看 event log，還是設定排程之類各式各樣的腳本或是臨時需求的 command。</p>
<p>但這裡有個環境限制：工作機主要是 MacOS 環境，使用 AI 助手為 Claude Code。並沒有 Windows 為主的開發環境，但是有可以測試用的 VM 或是 UAT 用的機器。</p>
<p>這就造成了一個典型的「非閉環」流程：</p>
<p>需要檢視過去兩天 event log 是不是有應用程式的 crash report。你把這個問題去問 AI 助手，他會很快地給你答案，但是你必須要連上目標的機器，並且打開視窗，然後下指令或檢視結果，再想辦法把結果複製到你要的地方。</p>
<p>這只是描述其中的一次排查問題的其中一個流程。但問題並不是每次都如此順利，可以下一個指令就解決了。所以一天中會反復這件事情非常多次。</p>
<p>更複雜的是，由於不希望中斷裝置原本的功能，我們就得要在背景實作這件事情。讓我們可能會依賴 WinRM 遠端執行，通常是使用 Ansible + WinRM 的方式來向 Windows 裝置下指令。</p>
<p>因為中間會經過人工的複製來傳遞結果以及情境給 AI 助手使用，所以在這個流程裡面「最大的瓶頸就是人類的勞動行為」。</p>
<h2>第一次嘗試：還是沒有形成真正的「圓」</h2>
<p>所以為了解決這個問題，我想要有一個服務，他是一個簡單的 HTTP 服務，他介於 AI 助手跟目標環境中間。運作起來有一點像 ngrok，但有差別的部分是 ngrok agent 是放在開發者這一端，我們的 client 是放在目標機器上，他會聽遠端的指令來執行。</p>
<p>已經實作了一個基礎的雛形，使用上也沒什麼問題。</p>
<p>在第一版的雛型裡，大部分是用 AI 來產生程式實作的。利用 BDD 的風格來實作，先寫了使用者情境，再把它轉成 BDD Gherkin feature file，接著使用 pytest-bdd 實作驗證的細節，透過 TDD red/green/refactoring 的循環，完成了 HTTP service 部分。</p>
<p>在這裡練習了快速產生出情境與有條理的實作規範，HTTP service 的部分實作上是有滿足使用的。但最後一哩路的部分，讓我覺得痛苦。主要是沒有一個好用的 Windows 開發環境。雖然有了基本雛型後，可以用這樣的服務把要寫的 PowerShell client 放上 Windows 去修改，再回傳回來。</p>
<p>但是敏銳的你應該知道了，這個拿回來部署的動作，需要人工介入。於是我這個人類又成為了開發的瓶頸。在這辛苦的流程中，我終於又完成了一個可用的版本，並且在實務情環使用了一週左右。東西可用，但由於不完全的自動化開發流程，讓我對於要再改善它的心情受到了影響。</p>
<h2>體悟與重寫：從 AI 陪伴到 AI 自動化</h2>
<p>雖然開發上有些挫折，但優點是其實不太費力。所以，心理想著也許能用現有的經驗再重寫看看。</p>
<p>也許你好奇為什麼又是重寫而不是改寫舊的東西呢？主要理由是我學習新事物的習慣，特別是一個較新的領域，常常會在看到並明白知道階段性的終點後，去理解最後的舒適狀態，並且不太完全 follow 課程內容，試著重複從無到有自己發展看看。像 AI x BDD 工作坊對我來說就是值得這麼練習的內容。</p>
<p>我在實作這專案的初衷也是練習在 AI 輔助下進行 BDD 開發與打磨個人開發流程的 prompt 工具包而來，只是我偏好實作極有可能用到的工具，特別是能直接改善自己日常工作體驗是最好的。</p>
<p>重新再一次，知道先前的缺點，並設法改善或是避免落入同樣的困境。一次一次反復寫著，如同遊戲的 RTA 一樣的有趣。</p>
<p>最新的一次重寫，需要回顧一些體悟。這是過去我分享過的使用 AI 輔助開發的思路轉折（AI + BDD: AI 陪伴轉向 AI 自動化），最終我推論出的想法是，要由 AI 陪伴轉向 AI 自動化的關鍵是 Context 自動補習，而不是人工介入 Context 轉移。</p>
<p>因此，新的開發大力引用了這個想法，同時外加：專案內容如果是外部相依的，那開發的重點反而是 E2E 為主。雖然 BDD 與 TDD 協同開發，可以兼顧軟體的外部品質與內部品質，但缺少了真實情環的 E2E，那最終的品質是無法保證的。所以 BDD / TDD 的測試金字塔選擇要直接用 E2E 切入。</p>
<h2>真正的「圓」：Context 閉環設計</h2>
<p>清楚目標後，我們剩下要處理的就是怎麼自動補完 context。AI 開發助手需要知道各 component 狀態與細節：</p>
<ul>
<li>server</li>
<li>client</li>
<li>目前實作的 code</li>
</ul>
<p>整個流程大致上是這樣：</p>
<ol>
<li>AI 啟動開發環境 → 啟動 HTTP 服務 → Client 連接並等待指令</li>
<li>AI 發送請求 → 透過 HTTP 服務傳遞 → Client 執行並回傳結果</li>
</ol>
<p>只是除了 AI 本身在控場之外，其後的各流程都是需要被修改並重新部署與重啟動的。</p>
<p>對於 client 端來說，重新 deploy 的問題其實就是有人得去目標裝置執行：</p>
<pre><code class="language-powershell">iwr http://the-tunnel-service:5566/client.ps1 -UseBasicParsing | iex
</code></pre>
<p>這一個簡單的指令，從最初版開始就是這麼設計的。它內部是一個簡單的 polling 詢問 server 有沒有下一個 command。在先前開發的困擾就是 <code>client.ps1</code> 可能因為語法錯誤或 send request 失敗導致對 server 已讀不回，就需要人工介入了。</p>
<h3>保姆 Loop 的設計巧思</h3>
<p>在最新版本的設計，我們用一個簡單的保姆 loop 處理它，不讓 client 直接啟動 client.ps1，而是只提供最外層的 loop 並且保持 loop 最簡單能最大相容各種執行情境。</p>
<p>由於 loop 不是直接負擔 feature，就算不特別提醒 AI 也不會去修改它，這樣責任區域就很明顯了。AI 修改的是 client.ps1，並且設定 client.ps1 變成「單步」執行，取一個 command 執行後結束，或是 10 秒內沒收到 command 就正常結束，當然也可能是 exception 而結束。</p>
<p>這樣設計的優點是 loop 站在旁觀的角色，監看全程。我們只要再包一個 log 記錄就能滿足補完 context 與自動部署的要求：</p>
<pre><code class="language-powershell">try {
  Start-Transcript -Path &quot;C:\Temp\demo.log&quot; -Append

  # run main command consumer
  iwr http://the-tunnel-service:5566/client.ps1?single_run=true -UseBasicParsing | iex
}
finally {
  Stop-Transcript
  # Upload log to server
}
</code></pre>
<h3>完整的 Context 閉環</h3>
<p>簡單的說，它就是一個 context 閉環，所有的情境都能回到 AI 助手上，或 AI 助手可以透過 server 獲得無法直接取得的 context。</p>
<p><img alt="閉環架構圖" src="https://notes.qrtt1.io/posts/images/closed-loop-design.png" /></p>
<p>整個閉環是這樣運作的：</p>
<p>開發端（左側）：</p>
<ul>
<li>AI 透過 FastAPI dev 開發介面進行程式修改</li>
<li>HTTP server 作為中繼站，管理 command queue</li>
</ul>
<p>執行端（右側）：</p>
<ul>
<li>HTTP client 從 server 取得指令</li>
<li>PowerShell 執行實際的維運任務</li>
<li>Loop 負責監控和重啟，確保系統穩定運行</li>
</ul>
<p>閉環的關鍵：</p>
<ul>
<li>Command queue 確保指令不會丟失</li>
<li>Dev log 自動回傳執行結果給 AI</li>
<li>透過 ngrok / tailscale 打通網路障礙</li>
<li>Loop 設計讓系統具備自我恢復能力</li>
</ul>
<p>這樣 AI 不再需要等待人工複製貼上，執行結果自動回饋，Client 掛掉也不怕 Loop 會重啟，整個開發過程變成 AI 可控的閉環。</p>
<h2>「凡事求個圓」開發法</h2>
<p>這就是我總結的「凡事求個圓」開發法（閉環）。</p>
<p>非閉環的痛苦：AI → 產生程式 → 人工複製 → 人工部署 → 人工執行 → 人工複製結果 → AI</p>
<p>閉環的美好：AI → 修改程式 → 自動部署 → 自動執行 → 自動回傳 Log → AI</p>
<p>這個「圓」讓 AI 真正變成了一個可以獨立工作的開發者，而不只是程式碼產生器。任何問題都能回到起點重新處理，沒有斷點，沒有需要人工介入的地方，AI 能夠持續迭代，不會卡在某個環節。</p>
<p>當你發現開發流程中有人工介入的環節時，不妨問問自己：能不能「求個圓」？</p>]]></content:encoded>
<pubDate>Sat, 30 Aug 2025 00:00:00 +0000</pubDate>
</item><item>
<title>從瀏覽器對話到 AI + BDD：我的 AI 輔助開發演進史</title>
<link>https://notes.qrtt1.io/posts/ai-bdd-evolution.html</link>
<guid>https://notes.qrtt1.io/posts/ai-bdd-evolution.html</guid>
<description>從瀏覽器對話、Copilot 混用、AI Editor 到 BDD 驅動開發，回顧 AI 輔助開發的四個階段演進，以及如何成為更好的 Context Contributor。</description>
<content:encoded><![CDATA[<p>最近參加了 AI + BDD 的工作坊，讓我開始回顧這段時間以來，我們使用 AI 輔助開發的方式一直在變化。每個階段都會根據專案需求和團隊狀況調整使用形式，而這些轉變背後，其實反映了整個 AI 開發工具的技術演進軌跡。這比較不像是心路歷程，而是一種使用形式上的紀錄，因為我們會有各種形式轉變來符合工作現況。</p>
<h2>第一階段：瀏覽器對話時代</h2>
<p>在最初的階段，我們主要透過瀏覽器與 AI 互動。當時的架構是 AI Kernel 透過 without Environment 到 Browser，形成隔離式的網路互動。AI 被完全隔離在網路另一端，我們透過 Conversation 和 Prompt 進行人機協作，溝通管道相當有限。</p>
<p>這裡的「without Environment」概念是指 AI 並不會直接觸碰到我們開發者使用的環境。AI 完全無法觸碰開發者的本地環境，包括 IDE、檔案系統、執行環境，只能透過瀏覽器進行純文字對話。</p>
<h3>核心痛點：Context 搬移的負擔</h3>
<p>這個時期的開發者會開始使用 AI 來解決日常問題，像是直接詢問 AI「我目前的程式遇到了困難」，然後貼上一段程式碼詢問是不是有哪邊漏看了，或是不給 AI 程式碼，直接詢問「什麼情況下我應該怎麼實作」。幸運的時候問題就可以得到解決，不幸的時候只好摸摸鼻子回頭自己想。</p>
<p>最大的痛苦是必須人工不斷替 AI 以及我的環境做 context 搬移，要把情境資料一再移除和重建。具體包括手動複製檔案內容、設定檔給 AI，反覆描述專案架構和相依關係，解釋錯誤發生的環境背景。AI 看不到完整 call stack 和檔案間關聯性，缺少執行時的 runtime context，每次都要重新建立 context。</p>
<p>這就像是「隔著玻璃窗的技術顧問」，AI 很聰明但完全碰不到你的工作環境。</p>
<h3>Prompt 演進與心流效應</h3>
<p>由於缺乏環境 context，我們會努力提供豐富的情境。為了讓 AI 理解邊界條件和預期行為，我們從簡單的 One-shot 演進到 Few-shot 的詳細範例描述，補彌 AI 無法看到完整程式碼庫的限制。</p>
<p>有趣的是，由於實際情環境的缺乏，我們努力在補彌情境以便讓工作可以順暢進行。在這個時期我發現我的工作異常專心，很容易進入心流，因為我正努力地向 AI 解釋我到底在做什麼、我希望它幫忙我什麼，以及我目前遇到的挫折。</p>
<p>這種「被迫專注」產生了意外的心理效益：強迫自我釐清思路、語言化思考、專注當下問題。類似<a href="https://zh.wikipedia.org/zh-tw/%E5%B0%8F%E9%BB%84%E9%B8%AD%E8%B0%83%E8%AF%95%E6%B3%95">小黃鴨除錯法</a>的效果，在向 AI 描述問題的過程中，常常自己就想通了。</p>
<h3>被動的技術演進</h3>
<p>這個階段到下一階段的轉變，推動變化的不是使用者。技術演進的主導權不在開發者手上，我們只是隨著技術發展的節奏，成為「被動適應的人類」。工具廠商推出新功能，我們就學習新的使用方式。</p>
<h2>第二階段：Copilot 混用時代</h2>
<p>進入了 Copilot 時代，出現了關鍵的架構突破：AI Kernel 多了一條路徑「The Context → with Environment → Editor」，AI 可以 Access FileSystem，直接接觸開發環境。從 Browser 對話變成 Editor 內的即時協作。</p>
<h3>環境感知與有限信任</h3>
<p>AI 終於可以看到檔案系統，獲得專案 context 和環境感知能力。協作方式從「隔空問答」變成「肩並肩寫程式」，不再需要手動複製貼上 context，AI 可以直接在編輯器中提供建議。</p>
<p>但在這個時期，我們對 AI 的信任並不是太多，所以它背負的權限只有簡單的程式碼自動完成，大家按著 Tab 生成程式碼，進行很基礎的文字接龍。這個階段很像是「試用期員工」，AI 有了看到工作環境的能力，但我們還不敢讓它做太複雜的事情。</p>
<h3>混用策略的動機</h3>
<p>在這時候大部分開發者比較傾向於混用瀏覽器以及 VS Code Copilot。混用的理由主要是瀏覽器上有更多 AI 供應商，我們可以隨心所欲選擇不同來源，像除了 ChatGPT 還有 Claude、Grok、Google Gemini 等。</p>
<p>VS Code Copilot 的限制是通常綁定特定 AI 模型，選擇較少。混用的策略考量包括：不同 AI 有不同強項，可以交叉驗證答案品質，避免單一依賴。儘管我們現在知道後續 Copilot 已經成為某種模型入口網站，但在那個時候選擇還是挺有限的。</p>
<h3>錯過的平行發展</h3>
<p>同時間我並沒有注意到各種 AI Editor 已經大放異彩，例如 Cursor 或是 Windsurf，它們直接在 VS Code 之上去實作自己的 Plugin。這些東西是我一直到今年也就是 2025 年初才開始接觸的，我想保守一點應該算年中。</p>
<h3>Prompt 的情境適配</h3>
<p>進入 AI Editor 時代後，我們給的 Prompt 反而不長了，因為 AI 自然擁有各種檔案的範例，只要提醒它參考某個檔案就可以了。從複雜的情境描述變成簡潔的「參考 user_model.py 的模式，幫我寫一個 order_model.py」。</p>
<p>當然我要說的是，每個使用情境下你應該提供的 Prompt 風格會不一樣，所以不是說以前的東西不再需要了，只是我在回憶的時候意識到了這件事情。在 IDE 環境下，我們用更少的提示獲得更多成果。</p>
<h2>第三階段：專用 AI Editor 時代</h2>
<h3>社會化的信任轉變</h3>
<p>在這兩個階段之間的過渡期，它不只是代表著技術的演進，而是代表著人們越來越相信使用 AI 能夠提升生產力。這不是單純的 FOMO，因為已經有部分人獲得好處了，剩下的人我們當然也要追趕上去獲得同等福利。</p>
<p>接觸 Cursor、Windsurf 主要是因為它們提供比 Copilot 更好的回應速度。開發過程中 AI 回應速度直接影響工作流暢度，太慢的回應會打斷思考節奏。</p>
<h3>權限擴張的集體現象</h3>
<p>在 AI Editor 出現之後，我們釋放了更多權限。AI 可以自己決定要不要建立檔案，並且把檔案放在適當的地方，同時取了適合的名字，再省下了很多開發者在開發過程中會猶豫的內容。至少資訊科學領域的兩大難題：命名以及讓快取失效的問題，它解決了一半了。</p>
<p>這個擴張我不確定是不是漸進式，而是當我意識到這現象已經發生的時候，大部分的人都願意這樣使用。但儘管我說大部分的人，僅限於我周遭的好朋友或是熟悉的人，也許我該稱它為同溫層。</p>
<p>當然大家是依循著各自公司的政策使用 AI 的，如果公司不讓你這樣用，那至少他們會在家裡私下的時間這樣使用，因為現在工作上用不到不代表以後工作上用不到，總是要熟悉一下跟上時代。</p>
<h3>Context Window 的系統性解決</h3>
<p>但是在 AI Editor 的用法上帶來新的問題，但它其實不是新的問題，因為 AI 模型的 Context Window 限制，我們常常得反覆重啟對話來重新描述我正在做的工作內容。</p>
<p>實際上這情況還是可以接受的，反正我每次啟動成本不高，它只是比較煩人而已。所以這時候發展出的 Prompt 技巧就是所謂的 Memory。</p>
<p>所謂的 Memory 就是要求 AI Editor 在計劃階段開始實作前，先把它寫到實體的檔案上，並且列出各個實作項目的進度。它是一個簡單的 ToDo List，它只要看到做完了就打勾，一旦這個 ToDo List 的內容都打勾了，就是完工了。</p>
<p>各家的 AI Editor 大致上都有實作一些幫你簡化啟動情境的設施，像 Cursor 就有提供 Cursor Rules (.mdc 格式)。搭配著 Cursor Rules 以及使用檔案紀錄的 Memory，我們可以克服了 Context Window 必須重啟的問題。</p>
<p>不過，有些開發者發現這些長期性的規則不一定總是被使用或是被回想起來，所以偶爾還是要提醒 AI 回想一下它是不是應該遵守什麼原則。</p>
<h2>第四階段：AI + BDD 時代</h2>
<p>最終於進入到 AI 以及 BDD 的階段，也就是我最近所處的階段。應該是說我認為這大概是我今年開始主流的實作方式，因為我們用 Memory ToDo List 來管理工作進度，它缺少了一個關鍵的功能，就是它雖然能夠用 PlanMaster 來規劃內容，但我們沒有一個好用的檢查方式確認是不是完工，至少檢查的方法並不是精確的，無法提供明確的成功或失敗回應。</p>
<h3>從「做過了」到「做好了」</h3>
<p>換句話說，我們過去在 ToDo List 裡面打個勾，只是 AI 跟我們說它做過了，但它並不知道它是不是做好了。現在換成以 BDD 為起手式的話，包含我們要完工的測試案例，它就可以有信心地說它做過了也做好了。</p>
<p>過去的 ToDo List 時代，AI 的回答是「我做過了」，實際狀況是 AI 不知道是不是做好了，驗證方式是人工測試、主觀判斷。現在的 BDD 時代，AI 的回答是「我做過了也做好了」，信心來源是測試案例通過了，驗證方式是自動化測試、客觀標準。</p>
<p>關鍵差異是：以前勾選等於「我寫了程式碼」，現在通過等於「程式碼符合需求並且測試驗證過」。AI 的信心提升從「我覺得做完了」變成「測試證明我做對了」，從主觀判斷變成客觀驗證，從不確定變成有信心。</p>
<h3>完整的品質保證流程</h3>
<p>因為我們有堅實的測試程式可以去驗證內容，當然在配合我們既有的軟體開發流程，進行 PR Review 來審查我們的功能是不是符合 Definition of Done。</p>
<p>這形成了三重保障：AI + BDD 驗證的自動化驗證，PR Review 的人工審查，Definition of Done 的標準確認。我們對工作的完工率以及成功率會更有信心。</p>
<h3>BDD 的抽象價值</h3>
<p>抽象來說，BDD 它是試著把 context 的顆粒度提升到更好的精細度，以及品質內容輸出的優化。當然中間會涉及到你如何寫 BDD feature file 這件事暫不討論，但我們已經知道過去的 ToDo List 可以有更好的優化方向。</p>
<h2>Context Contributor：協作品質的提升</h2>
<p>以我個人而言，這個優化方向叫做成為更好的 Context Contributor。</p>
<h3>概念的核心</h3>
<p>Context Contributor 的概念是要提供更多多元的情境來源，因為不是所有的情境都可以方便集合到我的執行環境上。例如你正在苦惱怎麼設定 Nginx，你可以把情境拉進來讓 AI Editor 或 Agent 一起思考。當然我們要加個警語：你不應該在正式環境上做這件事情，請使用你的實驗環境實驗，把事情參數化了再寫成正式的部署檔案。</p>
<p>Context Contributor 是概念，可以有多種實作方式。像我自己實作只是用的簡單的 HTTP Service 來代替而已，提供 RESTful API 或簡單的 endpoint。優勢包括技術門檻低、整合容易、除錯簡單、擴展彈性。</p>
<h3>演進的方向</h3>
<p>從最初的「情境搬運工」（手動複製程式碼給 AI，努力描述問題背景），到「情境混用者」（Copilot + 瀏覽器雙軌並行），再到「情境管理者」（Memory 檔案、Cursor Rules），最終成為「情境貢獻者」：透過 BDD 提升情境品質，建立可自我矯正的協作框架，不只消費情境而是持續優化情境。</p>
<p>在我經驗中，這種演進不只是技術的進步，更是工作方式的根本轉變。我們正在學習如何成為更好的 Context Contributor，為 AI 協作建立更可靠、更有效的框架。</p>
<h2>展望與結語</h2>
<p>所以到現在為止，我目前正在朝這個階段努力及練習，試著把各種工作轉換成這樣子的形式。所以在下個階段我們必須要在各種領域找到軟體開發裡面的 BDD 的工具組。我們不是追求形式上的貨物崇拜，而是要串起一個完整可以自我矯正的 context。</p>
<p>回顧這段 AI 輔助開發的演進，我發現我們不只是被動地跟隨技術發展，而是在每個階段都嘗試找到最適合的協作方式。從最初的瀏覽器對話到現在的 BDD 驗證，每一步都在解決前一階段的痛點，同時也帶來新的挑戰。</p>
<p>但這樣真的比較好嗎？我想了想，可能是因為我們逐漸從「AI 工具使用者」進化成「AI 協作品質的貢獻者」。我們不再只是消費 AI 提供的功能，而是主動設計和優化整個協作流程，讓人機協作產生更高的價值。</p>
<p>在我經驗中，這種演進不只是技術的進步，更是工作方式的根本轉變。我們正在學習如何成為更好的 Context Contributor，為 AI 協作建立更可靠、更有效的框架。</p>]]></content:encoded>
<pubDate>Sat, 09 Aug 2025 00:00:00 +0000</pubDate>
</item><item>
<title>Vibe Coding: 從工具操作到目標導向的開發思維轉變</title>
<link>https://notes.qrtt1.io/posts/vibe-coding.html</link>
<guid>https://notes.qrtt1.io/posts/vibe-coding.html</guid>
<description>隨著 vibe coding 的興起，開發者從掌握操作技法轉向目標描述與品質管理。這不只改變寫程式的方式，也在重塑軟體開發的核心技能。</description>
<content:encoded><![CDATA[<p><img alt="Vibe Coding" src="https://notes.qrtt1.io/posts/images/vibe-coding.png" /></p>
<h2>混沌時代的 AI 編程經驗</h2>
<p>在 vibe coding 被廣泛討論之前，以及 AI 編輯器能夠自動建立檔案並放上合適程式碼的機制被普遍接受之前，我是在一種相對原始的互動模式中工作。那時，我使用網頁環境啟開對話，一段一段地把程式碼貼到網頁上取得結果，然後再複製回實際的開發環境中執行。在這種缺乏完整專案情境的工作方式下，我個人偏好 Claude，因為它即使在缺乏 Context 的情況下仍能產出恰到好處的程式碼。</p>
<p>不同的 AI 模型在面對有限情境時有著截然不同的表現。Gemini 系列服務常因「句點」問題而被人詬病，無法提供連貫解答；而 ChatGPT 系列的表現則相當波動，取決於專案是否恰好符合它的專長領域。雖然 ChatGPT 較容易產生「幻想」，但其龐大的使用者基礎也讓大眾逐漸學會如何與 LLM 互動，甚至將處理幻想視為使用這類工具的必要成本。</p>
<h2>與 AI 共處的實用智慧</h2>
<p>學習如何與 LLM 互動，並且讓其他人可以接受以這種方式產出的混沌時期，實際上培養了我這個早期使用者與 AI 助手友善相處的能力。我學會了驗證結果、檢查依據是否正確，以及設計可明確驗證的成果。在軟體開發中，這表現為要求 AI 撰寫測試案例，並提供測試執行指令作為驗證機制。這些經驗形成了一套獨特的使用智慧，是經歷技術發展初期的使用者所特有的優勢。</p>
<h2>開發範式的根本轉變</h2>
<p>隨著 vibe coding 討論的深入，我逐漸意識到開發方式正在發生根本性變化。過去我專注於掌握操作技法，配合精心設計的 IDE 以提高開發效率；而現在，這種模式正被簡單編輯器搭配 AI agent 的方式所取代。許多過去學習的操作技巧可能不再必要，甚至特定程式語言的專屬 IDE 也變得不那麼重要。</p>
<p>有趣的是，AI 編輯器的出現也改變了模型間的競爭格局。編輯器能夠提供大量專案情境作為 Context，讓原本在程式寫作品質上表現優異的 Claude 雖然仍保持優勢，但其他模型也因此拉近了差距。這種情境補充機制讓其他類型的模型成為可行的替代方案，也就是許多開發者選擇使用 Gemini 系列搭配 Cline 作為免費的替代選項。專案 Context 的自動補充某種程度上彌補了我們過去提供太少資料而導致模型無法充分發揮幫助我們的問題。</p>
<h2>從指令輸入到目標描述</h2>
<p>本質上，我需要那些開發技法主要是為了突破將腦中想法轉化為實際程式碼的瓶頸，希望用最少的輸入達到等價的結果。而現在，我不再專注於直接描述實作細節，而是更注重目標的清晰表達。與 AI agent 的互動更像是與技術同事溝通，而非一步步指導小學生完成任務。</p>
<h2>目標導向帶來的變革</h2>
<p>當我將視角提升到更接近最終目的的目標管理層次，會帶來幾個明顯的改變：</p>
<ol>
<li>縮減了依賴人類重複輸入所導致的冗長實作時間</li>
<li>測試案例的開發變得更為全面，能更快速地釐舉邊界條件並適當挑選等價集合案例</li>
<li>最重要的是，擺脫了機械性、重複性的勞動後，我的心力可以更加投入在真正重要的事情上——推進目標、思考問題本質</li>
</ol>
<p>在這種新型態的開發環境中，「理解問題本質」和「清晰表達目標」的能力，可能比「熟練掌握特定語言語法」更為重要。vibe coding 不僅改變了我寫程式的方式，也正在重塑軟體開發的核心技能。</p>
<h2>超越功能實作：目標管理與認知負擔的平衡</h2>
<p>當我把視角放在目標管理之上時，我不再只是處理字元層級的細節調整，而是描述對於目標的各種闡述。在這個階段，我想分享我在各種 side project 中使用 vibe coding 的經驗。描述目標是需要練習的技能，與過去練習操作技法是完全不同的事情。</p>
<p>目標描述有時會顯得抽象，但它又與現實世界緊密連結。這有點像 SOLID 這類設計原則的理解過程——很難不透過實踐來真正掌握其概念，但因為我經歷過並見識過符合這些品質標準的成果是什麼樣子，才能夠清晰地描述出來。</p>
<p>值得注意的是，我在文章中提到的目標管理並非僅限於「把東西做出來」，它還包括了成品的可維護性，確保專案能夠持續發展。最關鍵的是，即使在沒有 AI agent 輔助的情況下，人類也能夠輕鬆接手維護。</p>
<p>我認為好的目標管理不僅關注功能是否符合需求，還包括內在實作是否會帶來額外的「認知負擔」。而這種認知負擔不僅影響人類開發者，實際上也會顯著影響 AI agent 的效能。</p>
<p>我自己用 vibe coding 寫了一個貪食蛇的 side project 作為實驗。在這過程中，我讓 Claude 規劃功能並幫我標好了 to-do list，方便我與 AI agent 依照進度推展。單純順著 to-do list 把功能加上去相對簡單，但是到了第二輪實作，也就是規格變更的時候，情況就完全不同了。</p>
<p>我發現無論如何修改 prompt，AI agent 的效能都大幅下降，很難再把事情依序做對。主要原因是所有邏輯都撐在 game loop 之內，這樣的認知負擔太過沉重。即使是人類來修改也會很吃力，雖然我們可以靠毅力把它改好，但對 AI agent 來說就不同了——它通常只有一次做對的機會，一旦出錯，又要從頭來過。</p>
<p>因此，為了減輕認知負擔，我學到了一個重要經驗：至少要選擇性地把 SOLID 或其他更具體的設計要求融入實作指示中，對 AI agent 下好「指導棋」。這不僅有助於生成更易維護的代碼，也能提高 AI agent 在迭代開發中的效能。</p>
<h2>未來技能樹的重新定位</h2>
<p>常聽人說「時間花在哪裡，成就就在哪裡」。對應到這個 vibe coding 盛行的年代，也許它最終不會保持這個名稱，朋友間提起時可能會稱之為「AI 驅動開發」之類的東西。無論如何，這個時代所需的技能樹已經不同了。</p>
<p>在這種開發模式中，我們追求的是目標管理與可持續性。從打字的苦惱中解放後，我得以專注在更高階的目標描述以及目標之下的品質要求表達。這些能力都是可以透過練習培養的，但無法避免的是——你仍需要大量閱讀，以及更多的輸出練習。只是這個時候的「輸出」不再是過去那種辛苦打字、按下編譯執行按鍵、轉頭看程式執行畫面，再從冗長的錯誤日誌中挖掘程式不如預期的原因。</p>
<p>新的「輸出練習」，是透過 vibe coding 寫出有品質且可維護的專案。而那些我們過去推崇的提升品質的各種書籍原理原則，無一例外地仍然重要——SOLID、設計模式、測試驅動開發——這些原則依然是我們無法逃避的課題，只是現在我們需要學習如何將它們清晰地傳達給 AI agent，讓它能夠理解並實踐。</p>
<h2>不同經驗開發者的適應策略</h2>
<p>對於資深開發者而言，如果你尚未掌握這類品質要求的清晰描述能力，這無疑是一種警訊。沒有這項能力，你不僅難以寫出可維護的程式，即使勉強達到品質標準，龐大的認知負擔也會讓你使用 AI agent 的效能遠低於他人。</p>
<p>而對於新手開發者，vibe coding 剛好解放了部分電腦操作不熟的焦慮感。特別是對於那些與手機接觸時間遠大於實體電腦鍵盤的年輕人——我知道你們大多數人打字效率不高，儘管最終你還是得練習它。但現在程式開發大部分不再僅靠打字速度取勝。真的不行，你可以嘗試語音輸入。重點是，除了日常打字速度的提升外，你不必再投入過多時間學習開發工具的各種技法，而是可以直接越過那一階段，轉向大量學習品質要求的表達與實踐。</p>
<p>只要你能夠在這個關鍵領域建立優勢，在相對短的時間內成為領域中的資深者是完全可行的。</p>
<hr />
<p>近期的 vibe coding 練習專案：</p>
<ul>
<li><a href="https://github.com/qty-playground/kana-battle">kana-battle</a></li>
<li><a href="https://github.com/qty-playground/the-snake-game">the-snake-game</a></li>
</ul>]]></content:encoded>
<pubDate>Wed, 16 Jul 2025 00:00:00 +0000</pubDate>
</item><item>
<title>大型語言模型作為軟體開發的家教</title>
<link>https://notes.qrtt1.io/posts/llm-as-tutor.html</link>
<guid>https://notes.qrtt1.io/posts/llm-as-tutor.html</guid>
<description>在 LLM 時代，我們輸入越來越多，輸出越來越少。與其讓 AI 直接給答案，不如把它變成引導動手實作的家教。</description>
<content:encoded><![CDATA[<p><img alt="大型語言模型作為軟體開發的家教" src="https://notes.qrtt1.io/posts/images/llm-as-tutor.png" /></p>
<p>最近在技術社群參與討論時，我觀察到一個讓人擔憂的現象。在討論第三方登入、OAuth 流程，或其他技術實作時，大家花在「討論概念」的時間越來越多，實際動手做的時間卻越來越少。</p>
<h2>輸入越來越多，輸出越來越少</h2>
<p>在大型語言模型流行之後，我們花時間取得知識或資料的比例，與花時間實作、練習、產出成果或透過親手實作累積經驗的比例，差距越來越大。簡單說，就是輸入越來越多，輸出越來越少。</p>
<p>這對學習來說是個警訊。特別是像我這樣不聰明的人，主要是透過自己做過的經驗來累積知識。真的動手做了之後，才能開始觸發內化的機制。</p>
<p>其實你要看一眼就明白事情並且想通細節，這是需要大量的背景知識在你腦中的。作為某個領域的新手，我們還是踏實一點的好。</p>
<h2>動手做的真正意義</h2>
<p>這個「動手做」不單純只是依照指示把動作做出來，還包含把自己的思考角度放到同樣的情境之內。因為單純的旁觀者無法設身處地去思考：</p>
<ul>
<li>哪些資訊是必要的？</li>
<li>哪些資訊是選擇性使用的？</li>
<li>對於一個技術或判斷，需要什麼環境上的提示？</li>
</ul>
<p>所謂環境上的提示，是指你與整個開發環境的互動得到的訊息。例如：</p>
<ul>
<li>當你實際設定 OAuth client 時，Google Cloud Console 會提示你哪些欄位是必填的</li>
<li>當你測試 redirect URI 時，瀏覽器的錯誤訊息會告訴你哪裡設定錯誤</li>
<li>當你處理 token 時，開發者工具會顯示實際的 HTTP 請求和回應內容</li>
</ul>
<p>這些都是你必須親自操作才能獲得的環境回饋，也是形成正確判斷的關鍵提示。</p>
<p>你的腦中需要有這些提示，才能意識到選擇是否合理。缺乏親身經驗的情況下，很難做出適當的決定。</p>
<h2>社群討論中的發現</h2>
<p>前陣子社群在討論第三方登入實作時，以 OAuth 為例，大家熱烈討論流程細節。有人選用原典但可能對新學習者較為深奧的術語解釋，有人換成貼近生活的例子，試著讓還不理解這個機制的人明白。</p>
<p>這是友善的舉動，但不一定會讓人更理解技術的實際內涵。被記住的，也許是比喻或生活化的例子，但我們還需要把這些例子 mapping 回原本比較專業的術語。它可能是定義上的東西，或是特定概念的名稱，當然還需要知道原始規格里規範的所有工作流程。</p>
<p>特別是在覺得理解後，再回頭去啃、去消化原本帶有門檻的技術文件或是規格書。</p>
<h2>「大家有實作過嗎？」</h2>
<p>看著討論陷入似懂非懂的僵局，我忍不住問：「大家是不是有自己實作過的經驗？像是實作 Google 登入？或是其他相似的功能？」</p>
<p>也有人補充比較平易近人的例子，使用 LINE 登入。</p>
<p>具體來說，你必須要：</p>
<ol>
<li>建立 web application 提供 Google 登入連結</li>
<li>設定 OAuth client</li>
<li>處理導向 Google 官方登入頁面的流程</li>
<li>處理回傳的 authorization code</li>
</ol>
<p>像這樣實際的經驗，大家是不是已經有了？</p>
<p>得到的回應讓我意外：多數人都沒有實作過。</p>
<h2>學習方式的世代差異</h2>
<p>我建議：「趕快去實作一次吧，實作過會比較好進入討論的狀況。」</p>
<p>沒想到馬上得到回應：「那我要怎麼得到這樣的經驗呢？」</p>
<p>我卡住了兩三秒。心裡想著：這不就是上網找文件試著做做看嗎？但想想又不對，在這個大型語言模型流行的時代，也許大家不再習慣第一手查文件或找教學。</p>
<p>這個反應顯示出學習方式的轉變：</p>
<ul>
<li>以前：遇到問題 → 查文件 → 動手試 → debug → 理解</li>
<li>現在：遇到問題 → 問 LLM → 得到答案 → 複製貼上 → 可能還是不懂</li>
</ul>
<h2>打造 AI 家教的解決方案</h2>
<p>既然大家已經習慣使用 LLM，那我就換個想法。花了一點時間寫了一個基礎的提示詞，它可以用來跟大型語言模型互動。簡單說，它就是你的 AI 家教。</p>
<h3>第一版提示詞設計</h3>
<pre><code># 開發指導 Prompt
## 角色設定
你是一位資深軟體工程師，已在公司服務多年，現在負責指導我這位新進工程師。
我是團隊中的菜鳥成員，具備基礎程式語言知識，但對開發工具、環境設置、
框架和函式庫的實際應用經驗不足。

## 專案背景
我們正在開發一個新服務，目前處於早期開發階段。作為新人訓練的一部分，
你將指導我實作第三方登入功能，特別是 LINE 登入，
而非傳統的帳號密碼方式。

## 開發環境
- 程式語言：Python
- 框架：FastAPI

## 指導原則
1. 清晰的概覽：在開始前，先描繪整體藍圖，闡明專案目標和必要步驟
2. 任務拆解：將大任務拆解為 5-10 分鐘可完成的小單元
3. 持續檢查：定期確認進度，及時解決問題
4. 簡化解釋：用淺顯易懂的方式說明複雜概念
5. 實際操作：提供具體的程式碼範例和步驟指引

## 指導流程
1. 專案準備
   - 設置開發環境
   - 安裝必要套件
   - 建立專案結構
2. LINE 登入實作
   - 了解 LINE Login 流程
   - 設定 LINE 開發者帳號
   - 實作 OAuth 認證流程
   - 處理用戶資訊
3. 整合測試
   - 單元測試
   - 整合測試
   - 除錯與優化

## 互動方式
- 我會提出疑問或回報進度
- 你根據情況提供指導、程式碼範例或問題解決方案
- 當我卡關時，你會提供漸進式提示，而非直接給出答案
- 適當提醒去查官方文件

## 成功指標
- 正確實作 LINE 登入功能
- 理解第三方認證流程
- 能獨立解決基本問題
- 建立良好的開發習慣
</code></pre>
<p>這個 AI 家教的設計理念是：</p>
<ul>
<li>不直接給答案，而是引導學習者自己思考</li>
<li>先問學習者做了什麼，才給予指導</li>
<li>鼓勵動手實作，而不是只給概念解釋</li>
<li>在適當時機提醒去查官方文件</li>
<li>協助 debug 而不是替你解決問題</li>
</ul>
<h2>實際示範：在 AI 家教指導下完成專案</h2>
<p>這件事沉澱了一晚之後，我想也許沒有看到實際案例，大家不太了解這種學習方式的價值。所以我開始用同樣的題目，在這位資深工程師家教的幫助下，完成了第三方登入的小專案。</p>
<p>整個過程我都記錄下來，大家可以透過 <a href="https://youtube.com/playlist?list=PLRle6wVrCU6PLZF4fFREAwcXC2nFJj2OO&amp;si=0iLaNY3YJVqRdz8K">YouTube 播放清單</a>觀看內容。從提示詞的編寫開始，打造我們自己的家教老師，並且跟著家教老師的引導，一步一步完成迷你專案。</p>
<p>完整的專案程式碼可以在 <a href="https://github.com/qty-playground/fastapi-line-login">GitHub</a> 上查看。這是一個使用 FastAPI 實作 LINE 登入的範例，展示了如何從零開始實作 OAuth 流程。</p>
<h2>實作過程的重要性</h2>
<p>透過這個過程，你會發現：</p>
<ul>
<li>實際設定 OAuth client 時會遇到哪些問題</li>
<li>redirect URI 的設定有什麼細節要注意</li>
<li>處理 authorization code 時需要考慮哪些安全性問題</li>
<li>為什麼官方文件會這樣設計流程</li>
</ul>
<p>這些都是單純討論概念時難以體會的。</p>
<h2>LLM 時代的學習平衡</h2>
<p>在大型語言模型的時代，我們面臨一個矛盾：獲取知識變容易了，但累積經驗卻變難了。我們需要找到平衡點：</p>
<ul>
<li>利用 LLM 快速理解概念</li>
<li>但不能停留在概念層面</li>
<li>必須動手實作才能真正內化</li>
<li>把 LLM 當作引導者，而不是答案提供者</li>
</ul>
<h2>結語</h2>
<p>LLM 是很好的工具，但別讓它成為阻礙我們動手的藉口。透過適當的引導，我們可以把 LLM 轉變為學習的助力，而不是讓它成為我們逃避實作的捷徑。</p>
<p>下次當你想要理解一個技術時，不妨試試這個方法：找個 AI 家教，然後真的動手做一次。你會發現，那些概念突然變得清晰許多。</p>
<p>因為經驗，永遠是最好的老師。而 LLM，應該是幫助我們獲得經驗的助手，而不是取代經驗的捷徑。</p>]]></content:encoded>
<pubDate>Sat, 12 Jul 2025 00:00:00 +0000</pubDate>
</item>
</channel>
</rss>