iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
AI Engineering

一天一個 PR,讓我告訴你嵌入式開發的殘酷:如何為 AI Agent 建立可信的工程閉環系列 第 13

Day 13 - 415 個 Case 不可能每次從頭來:Audit 也要會中斷續跑

  • 分享至 

  • xImage
  •  

系列說明 >> 本系列大部分 PR 來自私人或公司專案,部分程式碼、環境設定與實作細節不便公開。

我只能保證文中提到的 PR 與問題皆為真實案例,但會經過必要的匿名化與內容調整。

本系列主要分享問題如何被發現、背後的思考方式,以及解法如何形成,不會深入討論完整實作與部署細節,敬請見諒。

Today’s Change

  • Change Ref: PR #132-feat(audit): P3 可用性與文件收斂
  • Issue: 前面的 Audit Gate 已經能綁住 Case、Proposed、Source 與 Apply Artifact,但 415 個 Case 的長流程仍缺少好用的 Worklist、子集執行與 Resume;Operator 很難直接看出哪些 Case 已完成、哪些還需要處理,也很難在不破壞既有 Artifact 的情況下接著跑。
  • Root Cause: Audit 已經有持久化的分類 Bucket 與 Per-case Artifact,CLI 卻仍偏向「把 pass12 當一次完整操作」;既有進度沒有被正式拿來當 Resume Authority。
  • Solution: 加入 status --worklistpass12 --cases--resume;Resume 先檢查 Case 是否已進入任一 Bucket,再決定是否執行,避免清掉舊 Artifact 或重跑已分類 Case。pass12 同時持久化 Workbook Row/Expected Result,讓 Summary 以 Bucket 為 Authority 顯示每案狀態;並修正 Command Extractor 對真實 Workbook Prompt/Placeholder 的辨識。
  • Evidence: Audit 測試套件 256 passed;Policy Check 25 pass / 0 fail / 1 warn;Base0410 Dry Run 可列出 415 Cases 與各分類 Bucket 的 Worklist;真實 Workbook 的 Command Extractor 由 327 筆提升到 953 筆,約 2.91×。本 PR 驗證全程走零硬體路徑。

上一篇防完調包,老Go問了一個更煩的問題

上一篇最後,老Go看完 Before、Proposed、Target Drift、Source Root、PR SHA 那整套 Identity Chain,問了一句:

「所以現在可以放心讓它跑很久了?」

Agent 沒有立刻回答。

我也沒有。

因為老Go又換題目了。

Day 12 解決的是:

我現在驗的
是不是一路都是同一份東西?

但一套要處理 415 個 Case 的 Audit,還有另一個非常現實的問題:

如果今天沒有一次跑完呢?

這不算極端 Failure。

這叫流程很長,人會下班,Terminal 會關,事情不一定今天做完。

Audit 中間還有 Pre-classification、Pass 1/2、後續校正、Case-by-case Evidence 與 Decision。有些 Case 很快就能分類,有些會卡住,有些今天根本不想碰,我甚至可能只想重跑剛改過的那幾條。

如果每次進 pass12 都只能理解成:

「好,415 條重新來。」

那前面把 Audit 做得再嚴謹,實際操作起來還是很像一個不能存檔的 RPG。

很硬派。

我沒有很想玩。

規則很多。

Boss 很強。

關掉遊戲,從新手村開始。

這就不太行了。

Target


其實進度早就在,只是 CLI 沒把它當成一等公民

PR #132 並不是突然發明一個新的 Job Database,替每一條 Case 再建:

pending
running
done

沒有。

Audit 原本就有 Bucket。Case 經過 pass12 後,會依結果進到不同分類;前幾天的流程也已經會留下 RID、Per-case Artifact 與後續 Audit Record。

也就是說,進度早就在。

只是 Operator 看不到一張好用的工作清單,pass12 也沒有把「這條 Case 已經進了某個 Bucket」正式拿來決定要不要 Resume。

老Go看了一下設計。

「所以你不是再做一套 State?」

「對。」

Agent 補了一句:

「Bucket 仍然是 Authority。Worklist 只是把既有 State 展開。」

這句很重要。

最偷懶的做法,是新增一份 worklist.json,自己再記 pending/done,然後祈禱它永遠和 Bucket 同步。

我前面才講完 One Authority per Fact。

如果走到「進度」這裡又養第二份 State,前面幾天大概可以整段刪掉。

所以新的:

testpilot audit status <RID> --worklist

只是把各 Bucket 裡的 Case、Reason 與必要診斷資訊攤出來;needs_pass3 如果已有擷取到的 Command,也能直接看到數量。

不是重新算一次。

也不是另外養一本帳。

這功能真的很不性感。

但一個會跑很久的工具,如果連「現在做到哪」都要工程師自己 find + cat + jq 拼起來看,最後一定會有人選擇比較先進的方法:

憑印象。


--cases:不是每次都值得把 415 條叫起床

Worklist 能看了,下一個需求很自然。

我今天可能只想處理幾個 Case。

所以 pass12 加入 Case 子集:

testpilot audit pass12 <RID> --cases <case-ids>

這裡有兩條規則。

第一,指定的 Case 必須存在於這次 RID 的 Manifest。亂給不存在的 ID,不是默默忽略,直接 Fail。

第二,pass12 裡所有分支都必須使用同一份 Selected Cases。

不然很容易出現這種很自動化的結果:

你明明說只要三條。

它也真的只在某個 Loop 跑三條。

另一個 Loop 則秉持四海之內皆兄弟,把剩下 412 條一起帶上。

所以真正要守的是:

我這次到底叫了哪些 Case,整條 Flow 只能有一個答案。

Day 12 綁 Artifact Identity。

Day 13 開始連 Execution Scope 也不能靠大家心領神會。


--resume 的命,卡在「先判斷還是先清掉」

接下來才是這篇真正的主角。

testpilot audit pass12 <RID> --resume

看起來很普通。

已經分類的跳過,還沒分類的接著跑。

完。

問題是原本的 pass12 為了保持重跑的 deterministic behavior,重新執行 Case 時會先清掉舊 Artifact,再重新產生結果。

這個 Default Behavior 沒錯。

但 Resume 不能照同一個順序做。

如果流程變成:

先 wipe 舊 artifact
→ 再檢查這條是不是已經被分類
→ 啊,已經有結果了
→ skip

那這個功能名字雖然叫 Resume,實際比較接近:

Restart with confidence.

所以 PR #132 把順序釘得很死。

每一條 Case 進入執行前,先看它是不是已經存在於任一 Bucket。

如果已經 Bucketed:

先判定已有 Bucket
→ skip
→ 不清舊 Artifact
→ 不重新執行
→ 不重寫 Artifact

只有還沒被分類的 Case,才走原本執行路徑。

Resume 的命就卡在這個順序上。

先 Wipe,一切白講。

老Go問:

「那如果我是真的想重跑呢?」

「照原本跑。」

一般 pass12 仍維持原本 Re-run 語意;只有明確加 --resume,才把既有 Bucket 當成「這條已經完成本輪處理」的 Authority。

新功能沒有順手把舊 Contract 吃掉。

嗯。

至少這次沒有。

Target


Resume 不是少跑幾條,是前面的工程事實不能被你順手重做

Resume 這兩個字,我現在也不太敢只看名字就信。

很多工具所謂的 Resume,只是從某個 Index 往後跑。

畫面看起來接上了。

前面的 Artifact 有沒有被刷新、Log Timestamp 有沒有變,沒人知道。

Audit 不能只做到「少跑幾條」。

它還要確保:

已經 Bucketed 的 Case,真的沒有因為 Resume 又被動過。

所以 Regression Test 不只看輸出有沒有 skip

它會先讓 D001 進入 confirmed,再 --resume,確認底層執行沒有被再次呼叫、Bucket 沒多長一筆同 Case Entry,而且既有 Pass 1 baseline Artifact 沒被改寫,連修改時間都不變。

另一條測試則確認 Cases Directory 缺失時,已 Bucketed 的 Case 不會因為 Resume 又被追加新的 Block。

這才比較像我要的 Resume。

不是:

「我從第 38 條開始跑了。」

而是:

「前 37 條的既有工程事實沒有被我順手重做。」

只要這份進度會決定下一條到底跑不跑,它就不只是 UI。

這傢伙開始有權力了。


Summary 負責報,不要報著報著想升官

Audit 可以看 Worklist、跑子集、Resume 之後,還有一個問題:

如果每次想知道某條 Case 為什麼在這個 Bucket,都得一個個翻 JSON,Operator 很快還是會放棄。

所以 PR #132 也把 Summary 改成 Per-case 對照表。

這裡又有一條很便宜的歪路。

Summary 看起來只是 Report,所以最便宜的做法是產報表時再掃一次 Workbook、再找一次 Row、再比一次 Expected、再判一次狀態。

不要。

真的不要。

我已經受夠同一個 Fact 在不同 Layer 各算一次。

PR #132 選的是:pass12 當下就把 Workbook Row 與 Expected Result 留進 Pass 1 baseline。

後面的 Summary 直接顯示既有 Artifact;Case 的狀態仍以 Bucket 為 Authority。

RID Manifest
  → 有哪些 Case

Bucket
  → Case 現在在哪個狀態

Pass 1 baseline
  → 當時的 Workbook Row / Expected / Pass1 對照

Summary
  → 排在一起給人看

Summary 不重新發明一個「我覺得這條應該算 Pass」的演算法。

報表負責報。

不要報著報著突然想升官當 Verdict Engine。

不然過幾天我們就會開始 Debug:

CLI 說 needs_pass3
Bucket 說 needs_pass3
Summary 說 confirmed

然後三個人圍著螢幕討論,到底哪一個比較有道理。

我對這種民主制度沒有興趣。

工程事實最好還是一翻兩瞪眼。


Worklist 一攤開,才發現很多 Command 根本沒被看見

主線到這裡其實差不多了。

但 Worklist 與 Summary 變得比較能看之後,另一個原本被雜訊蓋住的問題也浮出來。

有些 Case 能抽出的 Command 太少。

不是 Workbook 沒寫。

是 Extractor 沒看懂。

真實 Workbook 裡很多 Command 會連 Shell Prompt 一起貼進去:

root@<target>:/# <command>

對人來說 Prompt 幾乎等於空氣。

對 Token-based Extractor 來說,第一個 Token 卻變成 root@...,後面明明是真 Command,整行還是被丟掉。

PR #132 因此在 Token 判定前先 Strip Prompt,但 Citation 仍保留 Workbook 原始行;Placeholder 規則也依實際資料補上 ${VAR}{i}wl[x]wlx 等形狀。

最後在真實 Base0410 Workbook 上,抽出的 Command 從:

327

提高到:

953

約 2.91 倍。

這個數字很漂亮。

但先別急著替它寫績效自評,說「Audit 效率提升 2.91 倍」。

它只證明:

Extractor 現在能從同一份 Workbook 看見更多原本就存在、且符合規則的 Command。

不是硬體快了 2.91 倍。

不是 Case 正確率高了 2.91 倍。

更不是 Agent 突然聰明了 2.91 倍。

有時候數字一漂亮,人就很容易開始替它安排職涯。

先不要。

Target


256 passed,證明的是 Contract,不是十幾個小時都不會出事

PR #132 的 Audit Test Suite 最後是:

256 passed

policy_check

pass: 25
fail: 0
warn: 1

那個 Warn 是既有 R-22 Advisory,不是這次新增 Failure。

另外也用 Base0410 做 Dry Run,確認 415 Cases 與 Bucket Worklist 能列出;Extractor 則直接讀真實 Workbook,確認 953 筆。

但這次整條驗證刻意沒有碰硬體。

不是偷懶。

至少這次不是。

是 Proof Boundary。

這個 PR 能證明的是 Worklist、--cases--resume、Per-case Summary 與 Extractor 的 deterministic behavior。

它不能證明一個真的跑十幾個小時的 Live Audit 從此永遠不會壞,也不能證明 DUT/STA/Serial 中斷後一定能自動恢復,更不能說 953 筆 Command 每一筆都應該直接拿去碰硬體。

Resume 的 Execution Contract 有被測。

長時間 Runtime Reliability 是另一件事。

把兩件事混在一起,很容易得到一句我們這系列已經看過很多次的話:

Tests Passed,所以系統應該沒問題。

不了。

這句我現在看到會過敏。


進度能保存了,下一個問題是:你保存的是哪一列?

回頭看這幾天,Audit 一直在把原本藏在人腦裡的東西拉出來變成 Artifact。

Day 10 綁 Case Location。

Day 11 把能離線判定的 Workbook Row 提前分類。

Day 12 綁 Before、Proposed、Source 與 Apply 的 Identity Chain。

Day 13 又往前推了一點:

連「這次做到哪裡」都不能只活在 Terminal Session 裡。

而且最好不要為了做到這件事,再發明第二套 Progress State。

既有 Bucket 就是 Authority。

Worklist 負責讓人看。

Resume 負責尊重它。

Summary 負責把相關 Artifact 排在一起。

至少在這條 CLI Contract 裡,流程終於有存檔點了。

不是每次回來都對著 415 條重新做人。

不過 Summary 現在也開始很認真地顯示:

Case
Workbook Row
Bucket
Expected
Verdict

看起來很好。

直到同一個 API 在 Workbook 裡出現不只一列。

那時問題就變成:

你這次保存下來的 Workbook Row,到底是哪一列?

更麻煩的是,有些列肉眼看起來一模一樣,實際字串裡還藏著看不見的 Unicode Format Character。

嗯。

存檔點是有了。

現在輪到存檔內容開始鬧鬼。

下一篇,我們來處理 27 條 workbook_row_ambiguous

同一個 API 對到多列時,到底該信誰。

Have a nice day.


上一篇
Day 12 - Agent 不能拿另一份 YAML 冒充已審核版本:不要作弊
下一篇
Day 14 - 同一個 API 在 Workbook 有 20 列:李逵?李鬼?傻傻分不清楚
系列文
一天一個 PR,讓我告訴你嵌入式開發的殘酷:如何為 AI Agent 建立可信的工程閉環21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言