AI agent 的「完成」,應該是一個可重跑的驗證迴圈

Ryan Vale
·
·
IPFS
·
Agent 的完成不該只是一句 done;把檢查、觀察、重試與 receipt 寫成可重跑的 verification loop。

Agent 改完了結帳頁的按鈕狀態。

Build 成功,測試也沒有紅字。它在回報裡寫下 done,附上修改過的檔案清單。直到有人把瀏覽器縮到手機寬度,才發現按鈕文字被擠成兩行;用鍵盤操作時,送出後的 focus 不見了;browser console 還留著一個 hydration error。

這些問題不一定表示 agent 能力差。更常見的原因是,團隊只交代了要改什麼,卻沒有把「怎樣才算改完」寫成它能執行的流程。

一句「請確認功能正常」對人已經很模糊,對 agent 更像一句禮貌提醒。它可能跑 build,也可能只看 diff;可能打開首頁,也可能完全不碰瀏覽器。每次執行都靠模型臨場解讀,結果自然難以重現。

小團隊真正需要的不是更長的 prompt,而是一條 verification loop:檢查、觀察、修復、再檢查,最後留下可供接手的紀錄。這條迴圈也必須知道何時停止,否則自動驗證很快就會變成自動亂改。

先把「完成」拆成幾種不同的判斷

程式能編譯、行為符合需求、團隊願意接受,原本就是三件事。

Build、typecheck、lint 和 unit tests 適合回答第一類問題。它們速度快、結果明確,也容易在本機與 CI 重跑。然而,一個全綠的 build 不知道按鈕是否擋住價格,不知道 loading 狀態會不會閃爍,更不知道產品經理是否接受新的互動節奏。

Preview、瀏覽器或 simulator 能補上肉眼可見的觀察表面,但「有打開畫面」仍然不是驗證。團隊得先指定要看哪個狀態、哪個尺寸,以及怎麼操作。若只留下首頁截圖,agent 很可能忠實完成這個動作,同時錯過真正改動的結帳流程。

至於是否接受變更,通常還包含產品取捨、風險與責任。這部分可以由 agent 整理證據,最後的決定未必要交給它。

每一類任務,都該有自己的 exit contract

我會把完成條件跟著任務放進 repo,而不是塞在某次對話的最後一段。

純函式修改可以偏重 unit tests 與 edge cases。UI 任務則要加入 viewport、互動路徑、keyboard 行為、console 和必要的畫面紀錄。資料 migration 還會需要備份、rollback、抽樣檢查與人工核准。把同一張通用清單套在所有任務上,只會得到很多形式完整、實際無關的綠燈。

以剛才的結帳按鈕為例,一份精簡的契約可以長這樣:

task: checkout-submit-state
scope:
  - components/CheckoutButton.vue
  - tests/checkout-button.spec.ts
checks:
  - pnpm typecheck
  - pnpm test tests/checkout-button.spec.ts
observe:
  viewports: [390x844, 1440x900]
  path: cart -> checkout -> submit
  inspect:
    - loading and disabled states
    - keyboard focus after failure
    - browser console
retry_limit: 2
escalate_when:
  - the same failure repeats
  - a fix requires files outside scope
  - expected product behavior is ambiguous

YAML 只是其中一種表示法。也可以用 script、skill,或 repo 裡既有的 task runner。只要下一個模型、下一位工程師與 CI 都找得到同一份完成條件,就不必重新猜測當時對話裡的語意。

把失敗結果送回去,不要只叫 agent「再仔細一點」

Verification loop 的第一輪通常不會全綠,這很正常。真正影響下一輪品質的,是失敗如何回到 agent 手上。

「測試失敗,請修正」幾乎沒有提供新資訊。比較有用的回饋會保留失敗命令、exit code、測試名稱、錯誤位置,以及必要的輸出片段。畫面問題也需要同樣具體:哪個 viewport、哪一步操作、預期看到什麼、實際出現什麼。Agent 才能針對證據修復,而不是再讀一次需求後換個方式猜。

每輪修復之後,至少要重跑受影響的 narrow checks,再視風險決定是否跑完整測試。只跑剛才失敗的那一項,可能漏掉新 regression;每次都跑整套昂貴流程,又會拖慢小改動。驗證契約應該把這個取捨寫下來,別讓模型自行選最省事或最保守的路。

迴圈必須防止 agent 偷偷改考題

有些全綠結果其實只是 agent 改了檢查,產品本身沒有修好。

它可能刪掉失敗測試、放寬 lint 規則、更新 snapshot 來接受錯誤畫面,或為了處理一個按鈕開始重構整個 form。單看最後的綠燈,這些做法甚至很像成功。

所以 verification loop 除了 checks,還要有 scope guard。每一輪都比較 diff 是否超出允許範圍;測試、設定與 snapshot 若被修改,就提高審查等級。需求不明、檢查互相矛盾,或修復需要跨越權限與部署邊界時,流程應停下來回報 blocker。

Retry budget 也很重要。相同錯誤連續出現兩次,通常不是再跑十次就會變好。可能是環境不完整、需求有矛盾,也可能是 agent 缺少判斷所需的產品資訊。此時把工作交回人,比讓它在凌晨持續擴大 diff 更接近負責任的完成。

一句 done 之外,留下一張 receipt

Reviewer 不該為了驗證 agent 的回報,再從頭探索一次 repo。每次迴圈結束時,可以留下短小的 receipt:

changed_scope:
  - components/CheckoutButton.vue
checks_passed:
  - pnpm typecheck
  - checkout-button.spec.ts
observed:
  - mobile and desktop submit flow
  - keyboard focus after failed submit
artifacts:
  - artifacts/checkout-mobile.png
remaining:
  - payment-provider sandbox was unavailable
retries: 1
human_decision_required: true

這張 receipt 不是自我認證。它只是把 agent 做過的檢查、看過的結果與仍然未知的部分攤開。Reviewer 可以沿著紀錄抽查,也能看出哪個決定還不能交給自動化。若流程中途換人或換模型,接手者也不用從聊天紀錄裡拼湊進度。

Receipt 還有一個很實際的用途:它讓團隊看見驗證成本。某個 UI 任務若每次都卡在同一個 setup,問題可能不在 agent,而在開發環境沒有穩定的 preview、fixture 或測試入口。久了之後,這些紀錄會指出最值得補強的 developer experience。

可攜的資產是驗證知識,不是某個模型的習慣

Agent、IDE 與模型都會換。今天有效的 prompt,下次可能因工具更新、context 不同或執行環境改變而失效。留在 repo 裡的 checks、觀察步驟、scope 與退出條件,才比較像團隊資產。

相較於一句「請 agent 自我檢查」,verification loop 把記憶搬出模型,也替失敗安排回饋與出口。畫面能打開,只代表觀察開始;是否可接受,仍要看事先寫下的條件與留下的證據。

當 agent 說完成時,我們需要的不是更有自信的句子,而是一條別人能重跑、遇到邊界會停止,也能把不確定性交回人的驗證迴圈。

Source notes

CC BY-NC-ND 4.0 授权

喜欢我的作品吗?别忘了给予支持与赞赏,让我知道在创作的路上有你陪伴,一起延续这份热忱!