一個 Session、7 個 Subagent、1,889 頁:TaiwanSalary 分階段實作紀錄
這是「Agentic Engineering 實戰手冊」系列第十五篇。上一篇:Agentic Engineering 的下一步
上週日下午,我想查「台積電非主管員工的薪資中位數」,結果卡在公開資訊觀測站的查詢頁:先選年度,再選市場與產業,想比較另一家公司就重來一次。
於是我做了工程師很常做的事:自己蓋一個。
當天晚上,TaiwanSalary 已經是一個 1,889 頁的純靜態網站。依當時 committed data 的快照,資料涵蓋民國 108–114 年、1,847 家曾出現在期間資料裡的上市櫃公司;114 年首頁可查 1,826 家。網站能搜尋、排序,也有公司歷年趨勢頁。README 記錄的 production build 約 3 秒、首頁 gzip 後約 100KB;這些是該專案當時的實測,不是 Astro 專案的普遍保證。
這篇想記的不是功能,而是它怎麼被做出來:主 session 當工頭,負責規格、驗收與整合;每一階段派一個 fresh subagent 實作。七個階段完成後,git history 也留下七個初始 commit。
工頭不負責多寫,負責看全局
我把工作切成資料管線、查詢頁、公司與產業頁、排行榜、SEO、文件等可獨立驗收的階段。每個 subagent 只拿到當前任務需要的 context,不需要背著整個專案一路走到底。
主 session 做四件事:
- 給清楚的交付物、限制與驗收方式。
- 確認 agent 還在工作,遇到中斷就恢復或重派。
- 讀核心 diff,跑資料與 build 驗證。
- 做跨階段決策,通過才 commit。
這個分工的好處不是「fresh agent 一定比較聰明」,而是每一段 context 較乾淨,出錯時也有 checkpoint。代價則是 handoff:前一個 agent 知道的事,不會自動出現在下一個 agent 腦中,工頭必須把關鍵決策寫下來。
七個 subagent 不是同時往同一個工作區衝,而是沿著同一套驗收閘門接力:
flowchart TD
A["主 session<br/>切出可獨立驗收的階段"] --> B["把交付物、限制、驗證方式<br/>交給 fresh subagent"]
B --> C["Subagent 實作<br/>並回報變更與證據"]
C --> D["主 session 讀核心 diff<br/>跑資料檢查與 production build"]
D --> E{"驗收通過?"}
E -->|"否:可延續"| F["補充證據或限制<br/>讓原 agent 修正"]
E -->|"否:無法恢復"| G["回到上一個乾淨 checkpoint<br/>重新派工"]
F --> C
G --> B
E -->|"是"| H["Commit checkpoint<br/>把決策寫進 repo"]
H --> I{"還有下一階段?"}
I -->|"是"| A
I -->|"否"| J["整體驗收與發布"]
這張圖裡真正不能省的不是 subagent,而是菱形那道驗收閘門。沒有讀 diff、跑檢查與留下 checkpoint,多開幾個 agent 只會更快把不確定性堆進同一個 repo。
那天有兩次,Subagent 突然沒動靜
第一次發生在資料管線階段。Agent 寫完 types.ts 後,我的 Mac 休眠、連線中斷。回來後,主 session 透過檔案 mtime 發現 27 分鐘沒有新輸出,便傳訊息確認狀態。那次 session context 還在,所以 agent 能從中斷處繼續。
第二次是後面的 API 連線中斷。我加了一個簡單的背景觀察哨,每三分鐘看一次是否有新檔案或新 log,確認恢復後才讓流程往下。
這裡不能推出「斷線後 context 通常都會保留」。是否能 resume 取決於工具、session 與中斷方式。真正能複製的做法只有兩個:保留 checkpoint,以及對長時間無輸出設 timeout 與人工升級,不要把沉默當成進度。
Review 抓到的,不只是 Agent 的錯
每個階段回報完成後,我都會讀核心檔案、跑對應檢查,再決定要不要 commit。這一步抓到兩個值得記的問題。
1. 工作區裡出現 NUL 位元組
build-data.ts 曾混入一個 \x00。它藏在字串裡,肉眼很難發現,而且那次 lint 與 build 沒有因此失敗。我另外掃描控制字元,才在 offset 6967 找到。
這不代表 agent 永遠看不到 NUL,也無法只憑結果斷定是哪一層寫入機制造成;能確定的是,那次執行沒有主動檢查控制字元。後來我把這類檢查放進資料管線驗證,而不是期待 reviewer 每次「順手想到」。
2. 重複公司代號會被靜默覆蓋
合併同年度、同股票代號資料時,程式原本會只留一筆。現有資料沒有同一家公司同時出現在上市與上櫃的情況,但如果未來來源資料異常,靜默覆蓋會讓錯誤很難追。
最後補的不是一句提醒,而是一條驗證:遇到重複 key 就讓 build 失敗。Review 最有價值的時刻,常常不是找到語法錯,而是把「目前剛好沒發生」改成可執行的不變量。
Agent 反過來證明我的規格錯了
MOPS 的薪資表版面會隨年度變動。我一開始很有把握地說:「108–112 年的三個旗標欄位,都用 V 表示 true、空白表示 false。」
資料 agent 沒有照抄。它掃過 14 份原始資料後回報:108、109 年是 V/空白;110 年起已經改為 Y/N。現在 repo 裡的 parseFlag 同時接受兩種格式,而 IMPLEMENTATION_PLAN.md 也留下這個決策。
如果照我的記憶做,110–112 年的旗標就會解析錯,而且不一定報錯。這次最重要的不是「agent 敢頂嘴」,而是它拿原始資料當證據。規格、人的記憶與模型的回答都只是待驗證假設,來源資料才是這個 parser 的權威。
資料管線也把不同年度的欄位數明確寫成 layout:108–112 年 16 欄、113 年 19 欄、114 年 31 欄。遇到未知年度或欄位數不符就 loud-fail,不猜欄位。README 提到 115 年預告會新增揭露欄位,因此下一次更新本來就預期要先人工檢查新 layout。
專案快照
- 1 個主 session、7 個階段、7 個 fresh subagent、7 個初始 commit。
- 1,889 個靜態頁面;當時 build 約 3 秒、首頁 gzip 後約 100KB。
- 7 個年度 × 上市/上櫃,共 14 份年度資料;期間合併後 1,847 家公司。
- 本機 Lighthouse 測試落在 96–100/100/100/100;分數會受頁面、Lighthouse 版本與測試環境影響,所以只當該次快照。
這套模式為什麼對這個專案有效
第一,階段之間有清楚的資料依賴,也能獨立驗收。第二,Astro 靜態輸出讓每一步都能用 build 檢查。第三,資料快照 commit 進 repo,上游 MOPS 暫時不可用時,網站仍能建置。
這不代表「fresh subagent 永遠勝過單一 agent」。如果任務需要大量隱性脈絡、切割後反而一直重讀同一批檔案,subagent 可能增加總 token 與 handoff 錯誤。這次有效,是因為階段邊界清楚,而且主 session 願意做整合工作。
另外,放手不等於甩手。我會看 timeout、讀 diff、跑 build,也會在規格被資料推翻時重新決策。Agent 做了大部分實作,但最終 merge 的責任沒有移出去。
想試 Subagent-Driven,可以先做這四步
- 挑能獨立驗收的階段。 不要只按檔案數硬切;每一段都要有交付物、成功條件與驗證命令。
- 把關鍵決策寫進 repo。 Plan、schema、ADR 或 README 都可以,別讓下一個 agent 只能靠主 session 口述。
- 每段完成就 review 與 checkpoint。 讀高風險 diff,執行對應測試,再 commit;不要因 agent 說「全綠」就略過。
- 設 timeout 與恢復路徑。 長時間無輸出就確認狀態;無法 resume 時,從上一個乾淨 checkpoint 重派。
這個專案還有兩條線值得另外寫:政府開放資料的版面與限流,以及為什麼我選純靜態 + committed JSON snapshot。它們更像資料產品題目,之後會放到台灣開放資料系列。
如果只留一句話,我會留這句:subagent 不是多開幾個聊天視窗就會自動變團隊;有人負責切工作、留證據、做驗收,它們才真的能接力。