[AI 實戰] 第一次用 Herdr:讓 Claude Code 當 Reviewer、agy 當實作者,在左右 Pane 協作完成 Go 後端 Code Review、5 個 Issue 修正與 Cloud Run 部署

1. [AI 實戰] 第一次用 Herdr:讓 Claude Code 當 Reviewer、agy 當實作者,在左右 Pane 協作完成 Go 後端 Code Review、5 個 Issue 修正與 Cloud Run 部署

[!abstract] 速覽

  • song-lingo 是我自己做來學日文歌的小工具:貼上一支 YouTube MV,Gemini 會幫我聽寫歌詞、標註羅馬拼音、繁中翻譯與文法,再用 Gemini TTS 產生「老師示範音」;我錄音跟讀之後,Gemini 會轉錄我的發音並逐字評分。
  • 改寫完、能動、測試也過了。但「自己寫、自己審」總是有盲點。
  • 讓 Claude Code 在左邊當主控與 Reviewer,讓 Antigravity CLI( agy )在右邊當實作者,兩個 Agent 透過 Herdr 互相傳訊,把 song-lingo Go 後端從 Code Review 一路做到 Cloud Run 上線。
  • Claude Code 把 17 項整併成 5 個 Issue(#57–#61) ,全部在同一個 branch go-review-fixes 上進行, 一個 Issue 一個 commit ,commit 訊息帶 Fixes #N 。
  • 回頭看這三個被退件的 Issue,有一個共通點: 它們全部都是「測試會過、但上線才會痛」的問題 ——測試缺口、前端錯誤訊息 regression、關機順序。這正是「一個寫、一個審」最大的價值。
  • 第一次用 Herdr,最大的感想是: 多 Agent 協作終於「看得見」了 。
Herdr 視窗截圖:左側 Claude Code pane 正在派任務,中間 Antigravity(agy)pane 顯示 Go 後端效能提案的檔案內容,右側還有另一個 Claude Code pane

1.1. 前情提要

song-lingo 是我自己做來學日文歌的小工具:貼上一支 YouTube MV,Gemini 會幫我聽寫歌詞、標註羅馬拼音、繁中翻譯與文法,再用 Gemini TTS 產生「老師示範音」;我錄音跟讀之後,Gemini 會轉錄我的發音並逐字評分。

前幾天,我才在同一個 Claude Code session 裡把整個後端從 Python + Next.js API routes 全部改寫成 Go 1.26 (Issues #44–#49、Tracking Issue #50、PR #51–#56)。改寫完之後的架構長這樣:

  • Next.js 只剩前端 ,用 rewrite 把 /api 轉給同一個容器裡的 Go server( 127.0.0.1:8081 )。
  • 部署在 Cloud Run( asia-east1 ) ,資料放在 Cloud Storage bucket,透過 gcsfuse 掛在 /data ;學習進度存在 Firestore ;前面用 IAP 限制只有我能登入。
  • Go 版的幾個重點: genai SDK、Gemini REST(voices / interactions)、Firestore transaction、用 kagome 斷詞轉平假名、自己寫的 Hepburn 羅馬拼音表,以及把 Python difflib.SequenceMatcher 移植成 Go(用 400 組 Python 產出的 golden 測試 對齊評分)。

改寫完、能動、測試也過了。但「自己寫、自己審」總是有盲點。剛好這次想試試 Herdr ——一個讓多個 AI Agent 在同一個視窗裡、各自佔一個 Pane 協作的工具。於是我做了一個實驗:

[!info] 這次的實驗設定
讓 Claude Code 在左邊當主控與 Reviewer,讓 Antigravity CLI( agy )在右邊當實作者,兩個 Agent 透過 Herdr 互相傳訊,把 song-lingo Go 後端從 Code Review 一路做到 Cloud Run 上線。


1.2. Herdr 是什麼?第一次用的上手筆記

1.2.1. 兩個執行檔:herdr-app 與 herdr-core

Herdr 分成兩層:

元件 角色 說明
herdr-app 桌面 App 管理 spaces > tabs > panes 的畫面配置,可以把 pane 往左右切、直接在新 pane 裡跑程式
herdr-core (CLI 叫 hc ) 終端機核心 負責跑「 不會因為視窗關掉而死掉的 terminal 」,提供 run / send / wait / read / keys / get 等指令讓 Agent 操作其他 terminal

一個小插曲:我一開始叫 Claude Code「先使用 herdr --skill 了解」,結果 herdr 這個指令根本找不到——實際的執行檔是 ~/.local/bin/herdr-app 與 ~/.local/bin/herdr-core 。

1.2.2. --skill:寫給 AI Agent 看的操作手冊

我覺得 Herdr 最聰明的設計是: 兩個 CLI 都有 --skill 參數 ,執行 herdr-app --skill 與 herdr-core --skill 會印出一份「給 AI Agent 看的操作手冊」。

所以 Claude Code 的第一步不是亂試指令,而是 先把兩份手冊讀完再動手 。手冊裡寫了很多很有分寸的規範,例如:背景 Agent 一定要 --bg 、要給名字與明確範圍、不要讓子 Agent 再開子 Agent、等待時用 hc wait 而 不要 sleep 輪詢 。

1.2.3. 核心指令速查表

指令 用途
$HC_TERMINAL_ID 每個 terminal 都有位址,Claude Code 自己是 t4
hc run --bg --name xxx <agent> "<task>" 在背景開一個有名字的 Agent 並交代任務
herdr-app do pane.split --direction right --pane <id> --argv [...] 在畫面上把 pane 往右切,並直接在新 pane 裡跑程式
hc send <t> "..." 貼上文字並按 Enter,用來派任務、給 Review 意見
hc wait <t> / hc wait --children all 等對方做完(取代 sleep 輪詢)
hc read <t> --lines 0 讀對方目前的整個螢幕
hc keys <t> enter 送出按鍵,例如回答權限對話框
hc get <t> 查狀態: idle / working / blocked / done ; blocked 時還會附上 msg ,例如 Approve RunCommand: ...

1.2.4. Agent 對 Agent 的訊息通道

這是整個協作最關鍵的一塊。我在交代任務時會要求 agy:「做完請執行 herdr-core send t4 '...' 回報」。agy 做完之後,那段訊息會 直接出現在 Claude Code 的輸入裡 ,長得像這樣:

<hc from="t7">ready for review #57 ...</hc>

換句話說,Claude Code 不需要一直盯著右邊的螢幕,agy 做完自己會「敲門」。回報是非同步的、而且每一則都帶著來源位址,可以追蹤。

1.2.5. 畫面配置

我第一次讓 Claude Code 開 agy 時,它是用 hc run --bg 在背景跑的——功能上沒問題,但我看不到 agy 在做什麼。所以第二次我特別要求「 in a pane on the right 」,Claude Code 改用 herdr-app do pane.split 把 agy 開在右邊。最後的配置是:

Herdr 視窗(同一個 tab)

hc send:派任務 / Review 意見

herdr-core send t4:ready for review #N

hc keys:放行唯讀權限

hc send:寫部落格

herdr-core send t4:blog draft ready

左 Pane:Claude Code (t4)
主控 / Reviewer
開 Issue、看 diff、跑 go test -race、批准

中 Pane:agy (t7)
實作者
Code Review、改程式、跑測試、commit/push

右 Pane:Claude Code
Blog Writer
(寫這篇文章的那一位)

我(人類)
看得到每個 Pane、隨時可插手


1.3. 第一回合:讓 agy 當 Reviewer

第一個任務很單純:請 agy 審 backend/ 底下的 Go 程式碼, 唯讀、不開 subagent ,把結果寫到檔案,完成後 hc send t4 回報。

agy 的介面顯示它跑的是 Claude Sonnet 5.5(plan 模式) ,最後產出了 17 項問題,依重要性排序 。Claude Code 幫我整理成三大類:

類別 agy 找到的問題
併發與資料正確性 Re-annotate 期間(一次 Gemini 呼叫最長 5 分鐘)若我在校對頁修改歌詞,寫回時會被蓋掉——兩邊用的是 不同的 mutex ; EditLine 的全域鎖跨過了 Firestore 呼叫; WriteAtomic 的暫存檔名固定是 .tmp
資源上限與洩漏 TTS 產生沒有並行上限(這可是付費 API);失敗快取只增不減而且重複存兩份;429 沒帶 Retry-After 時完全不限流; TeacherVoice 每次重讀 voices.json 且在網路呼叫期間持有全域鎖;request body 沒有 MaxBytesReader ;背景 job 用 context.Background() ,關機時不會被取消
效能與結構 listSongs 依序解析所有歌、逐首讀 progress;示範音整個讀進記憶體再回傳;同樣的 Error 型別有 3–4 份;CLI 的 speak 和 tts.Service 邏輯重複

[!warning] Reviewer 的結論也要被 Review
這裡有一個我很喜歡的細節: Claude Code 沒有照單全收 。在開 Issue 之前,它先自己 grep 驗證最重要的那一條——「re-annotate 與 review 用的是不同 mutex」——確認屬實,才開始動手開 Issue。Reviewer 的結論也需要被 Review。


1.4. 第二回合:開 Issue → agy 修 → Claude Code 審 → 通過才 commit

我給 Claude Code 的指示是:

「開 issue 然後依序修改,主要讓右方 agy pane 去修改,你負責 review 給意見,你通過才讓 agy 去 commit and push。」

1.4.1. 17 項整併成 5 個 Issue

Claude Code 把 17 項整併成 5 個 Issue(#57–#61) ,全部在同一個 branch go-review-fixes 上進行, 一個 Issue 一個 commit ,commit 訊息帶 Fixes #N 。

1.4.2. 每一輪的協作流程

GitHub agy (t7) 實作者 Claude Code (t4) Reviewer GitHub agy (t7) 實作者 Claude Code (t4) Reviewer alt [有問題] hc send:Issue 實作 + gofmt -l. && go vet./… && go test./… herdr-core send t4 'ready for review git diff + 自己再跑 go test -race hc send:Review 意見 修正後再次回報 批准,可以 commit & push git commit(Fixes 回報 commit sha

1.4.3. Review 實際抓到了什麼?

這是整篇最想分享的部分。5 個 Issue 裡, 3 個需要第二輪修正、2 個一次通過 :

Issue 主題 Review 結果 Commit
#57 Per-song lock 設計正確,但 沒寫測試 → 要求補 e5df5f4
#58 TTS 並行上限與失敗快取 抓到 錯誤訊息 regression → 要求修正並補測試 d67ec5a
#59 Request 上限、timeout、優雅關機 抓到 關機順序與時間預算錯誤 → 重新設計 de38741
#60 Song list 並行、示範音串流 ✅ 一次通過 bc1615a
#61 統一錯誤型別、CLI 重構、signal 處理 ✅ 一次通過 deb1fb7

1.4.4. #57:設計對了,但測試在哪?

agy 的 per-song lock 設計是對的。Claude Code 還特別確認了一件事:手動翻譯與 reviewed 旗標也都會寫進 transcript,所以「寫回前比對整份 transcript」確實能抓到所有編輯。

但 agy 沒寫測試。於是 Claude Code 要求把邏輯抽成 saveAnnotation ,並補上兩個案例: 未被變更時正常寫入 、 被編輯時回傳衝突且不寫入 。

1.4.5. #58:刪掉一個 map,前端就不知道為什麼失敗

agy 在整理失敗快取時,把 server 端的 recentErrors 刪掉了。測試全過,但 Claude Code 抓到一個 regression:

[!danger] 測試全過,前端卻壞了
當 TTS 額度用完時, 「其他從來沒產生過的句子」 去查 ?error ,會拿到 {error: null} ——前端就沒辦法顯示「額度用完」的原因。

修正方式是讓 LastError 在 沒有該句失敗紀錄時,回傳仍然有效的 quota 錯誤 ,並補上測試。

1.4.6. #59:Cloud Run 只給你約 10 秒,你卻排了 20 秒

這是我覺得最有價值的一個抓蟲。agy 寫的優雅關機是: srv.Shutdown 等 10 秒,再等背景 jobs 10 秒。問題有兩個:

  1. Cloud Run 送出 SIGTERM 後大約只有 10 秒 ,兩段加起來根本超過預算。
  2. 重新分析是同步跑在 HTTP request 裡的 。 srv.Shutdown 會等它結束,但它的 context 還沒被取消——等於自己等自己。

Claude Code 要求改成 單一 9 秒期限 ,並調整順序:

// 單一期限,留一點餘裕給 Cloud Run 的 SIGTERM 寬限期
ctx, cancel := context.WithTimeout(context.Background(), 9*time.Second)
defer cancel()

runner.Cancel()      // 1. 先取消背景與進行中的分析工作
srv.Shutdown(ctx)    // 2. 再關 HTTP server(同步跑的分析已經被取消,不會卡住)
runner.Wait(ctx)     // 3. 最後在同一個期限內等 jobs 收尾

(上面是示意,表達順序與單一期限的概念。)

1.4.7. #60、#61:一次通過

  • #60 :Song list 改成 bounded concurrency 8 ,單首 progress 讀取失敗不會拖垮整個清單;已產生的示範音改用 *os.File + ModTime 交給 http.ServeContent 串流(順便拿到 Range 與 Last-Modified)。
  • #61 :統一錯誤型別為 apierr.Error 、CLI speak 改用 tts.Service.GenerateAll 、改用 signal.NotifyContext 、Daily quota 判斷改用型別欄位而不是字串比對。

回頭看這三個被退件的 Issue,有一個共通點: 它們全部都是「測試會過、但上線才會痛」的問題 ——測試缺口、前端錯誤訊息 regression、關機順序。這正是「一個寫、一個審」最大的價值。


1.5. 第三回合:PR、合併、部署與驗證

5 個 commit 都通過後,開了 PR #62 合併,#57–#61 自動關閉。接著用 gcloud run deploy --source . 部署新 revision。

部署後的驗證是 Claude Code 透過 已登入 IAP 的 Chrome 直接跑 fetch 完成的:

驗證項目 結果 對應改動
首頁 /api/songs 200 (10 首) #60 並行讀取
學習頁 200 —
已產生的示範音 200 audio/wav #60 串流
示範音 Range 請求 206 #60 http.ServeContent
帶 If-Modified-Since 304 ( 新功能 :現在有 Last-Modified ) #60 ModTime
PATCH 塞 70 KB 413 「請求內容太大。」 #59 MaxBytesReader
Cloud Logging 只有兩筆 WARNING,都是測試請求本身 —

上一版 revision 也保留著,隨時可以切回。


1.6. 第一次用 Herdr 踩到的 6 個魔鬼細節

1.6.1. 細節 1:agy “prompt” 會直接 exit 2

直覺上會這樣開 agy:

agy "請審 backend/ 的 Go 程式碼..."

結果直接報錯退出(exit 2):

Prompts are read only from -p/--print, -i/--prompt-interactive, or stdin

正確做法是 agy -i "..." ——互動式啟動並帶入初始任務。Herdr 的手冊也提醒 不要用 -p 一次性模式 ,因為 Agent 要保持「可觀察、可再追問」,才能接著做 Review 來回。

1.6.2. 細節 2:第一次進資料夾的 Trust 對話框

第一次在某個資料夾啟動 agy,會跳出「 Do you trust the contents of this project?」。這時 hc get 的狀態會變成 blocked (auth)。由 Claude Code 用 hc keys go-review enter 確認即可。

1.6.3. 細節 3:看起來沒送出的 prompt,其實已經送到了

用 pane.split 開出 agy 後,畫面看起來是 idle,初始 prompt 好像沒送出去。Claude Code 想用 hc send 補送一次,結果被 hc 擋下來:

t7 is blocked (permission)... typing would answer its dialog; read its screen first

原來任務早就送到了,agy 正卡在等權限。 hc 這個保護機制,避免了把一段任務文字誤打進權限對話框 ——如果真的打進去,後果可能是莫名其妙地同意或拒絕了某個指令。

1.6.4. 細節 4:Plan mode 只能看、不能改

agy 在 plan mode(唯讀研究)時只能分析。要它開始改程式,必須按 shift+tab 切換模式;Claude Code 用 hc keys t7 shift+tab 幫它切。第一回合的 Code Review 剛好適合 plan mode,第二回合要修改時才切出去。

1.6.5. 細節 5:權限放行 Driver——從「看狀態列」進化到「讀整個畫面」

agy 每一個 shell 指令都要權限。一個一個按 Enter 太累,所以 Claude Code 寫了一個小 driver script,邏輯如下:

idle / done

blocked

是

否

是,且沒有 ALLOW_GIT=1

否,或 Reviewer 已批准

hc wait t7

hc get t7 看狀態

結束,等 agy 回報

hc read 讀出完整的權限畫面

含危險指令?
rm -rf / git reset / git checkout
–force / curl / gcloud 寫入操作…

停下來,交給人決定

是 git commit / push?

hc keys t7 enter 放行

這個 script 在過程中被修了幾次,每一次都是很實際的教訓:

  1. 狀態列的 msg 會被截斷 :一開始只看 hc get 回傳的 status msg,但它會被截成「…」,截斷的部分可能藏著第二個指令。改成 hc read 讀整個權限畫面 再判斷。
  2. gcloud A; gcloud deploy 單行 grep 會漏判 :改成把組合指令拆開、 逐條判斷是否唯讀 ( describe / list / read / ls / du )。
  3. 誤擋也要修 : gcloud storage ls 、 gcloud storage du 其實是唯讀的,一開始被擋下來,後來加入白名單。

1.6.6. 細節 6:權限放行的分寸

自動化放行的界線是這樣畫的:

  • 唯讀指令 :自動放行。
  • git commit / push :預設擋住, 只有 Claude Code Review 通過後 才用 ALLOW_GIT=1 放行。
  • 需要打外部 API 的指令 (例如 agy 想用 access token 打 Google Developer Knowledge API 查文件):停下來,由人決定。

[!important] 放行的界線
Agent 之間可以互相信任到「你可以跑測試」,但「寫入 git、寫入雲端、對外發請求」這種事,一定要過 Reviewer 或人。


1.7. 多 Agent 協作自我檢查清單(Checklist)

如果你也想用 Herdr(或類似工具)讓兩個 Agent 一寫一審,可以參考這份清單:

  • 1. 先讀手冊再動手 :讓主控 Agent 先跑 herdr-app --skill 、 herdr-core --skill ,確認實際執行檔路徑。
  • 2. 子 Agent 開在看得到的 Pane :用 pane.split 把實作 Agent 開在旁邊,人隨時可以看、可以插手。
  • 3. 用互動模式啟動 : agy -i "..." ,不要用位置參數,也不要用 -p 一次性模式。
  • 4. 任務範圍要明確 :唯讀與否、不要開 subagent、「不要 commit」、完成後用 herdr-core send <主控位址> 回報。
  • 5. 用 hc wait ,不要 sleep 輪詢 ;送文字前若對方是 blocked ,先 hc read 看畫面。
  • 6. Reviewer 的結論也要驗證 :開 Issue 前先 grep 確認最重要的發現屬實。
  • 7. Reviewer 自己再跑一次測試 :例如 go test -race ,不要只看實作者的回報。
  • 8. 權限 Driver 讀整個畫面 :不要依賴會被截斷的狀態列;組合指令要逐條判斷。
  • 9. Git 與雲端寫入要閘門 :commit/push 只在 Review 通過後放行;外部 API 呼叫交給人。
  • 10. 部署後實測新行為 :不只看 200,也要驗證新加的限制(413)、新加的標頭(304 / 206),並保留上一版 revision。

1.8. 下一步:讓 agy 分析 Cloud Run 效能

這篇寫完的同時,右邊的 agy 已經在跑下一個任務了:用它的 GCP skill 分析 song-lingo 在 Cloud Run 上的執行效能——服務設定、cold start、gcsfuse、Firestore 等——提出 5–8 個方案,每個方案附上證據、預期效益、每月成本與風險,再由 Claude Code 決定要修哪些。這部分還在進行中,之後再來分享。


1.9. 小結

[!quote] 最大的感想
第一次用 Herdr,最大的感想是: 多 Agent 協作終於「看得見」了 。

  1. 一個寫、一個審,比自己寫自己審更容易抓到盲點 :5 個 Issue 裡有 3 個被退件,抓到的全是「測試會過、上線才會痛」的問題——測試缺口、錯誤訊息 regression、Cloud Run 關機順序。
  2. Agent 對 Agent 的訊息讓協作非同步又可追蹤 : herdr-core send t4 'ready for review #N' 讓實作者自己來敲門,Reviewer 用 hc wait 等待,不用輪詢。
  3. Pane 讓人保持在迴圈裡 :我可以在右邊即時看到 agy 正在跑什麼指令,必要時直接插手。
  4. 權限放行要有分寸 :唯讀可以自動化,寫入 git、寫入雲端、對外發請求,一定要過 Review 或人。

而這篇文章本身,也是 Herdr 裡最右邊那個 Pane 的 Claude Code 寫的——寫完之後,它會用 herdr-core send t4 跟左邊的主控回報一聲。


Buy Me A Coffee 贊助按鈕(橙色,含咖啡杯圖示)