iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
AI Engineering

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

Day 19 - Plan 被退件四次,我卻連它寫了什麼都看不到:Planner 到底在負責什麼?

  • 分享至 

  • xImage
  •  

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

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

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

Today’s Change

  • Base Repo: paulsha-cortex

  • Change Ref: PR #513-fix(coordinator): planning artifact 拒收帶上原因與內容 evidence;背景 Issue #511

  • Issue: Planning Artifact 被 Gate 拒收時,Operator 只看得到一句 planning artifact is not accepted。真正的拒收原因、阻塞內容與 Planner 寫出的原文都沒有留下,現場只能 abandon、重新 claim,再把同一條流程重跑一次;實測連續四次得到相同失敗。

  • Root Cause: ArtifactAssessment 原本已經帶有 reasonsblocking_markers,Manager 卻只取 accepted 布林值;被拒內容又只存在 Temporary Directory,Context 結束後就消失。Planning 被當成一次性的中間文字,而不是一份必須可檢查、可拒收、可診斷的工程 Artifact。

  • Solution: 拒收訊息帶上有界的 reasonsmarkers 與 Evidence Path;同時將被拒 Artifact 以 cortex-planning-artifact-rejection/v1 寫入 Coordinator Root,保留內容、拒收理由、行號、Work ID 與 Run ID。Evidence 寫入失敗時採 fail-open,不得掩蓋原本的拒收結果。

  • Evidence: 新增 14 條 Regression Tests,完整測試結果為 2517 passed, 49 subtests passed,Policy Check Exit 0。這些證據支持拒收診斷與 Evidence 留存已接入既有 Planning Flow;本文不把它寫成 Planner 從此都能產出正確 Plan,也不代表 Issue #511 的回答通道與自動 Recovery 已全部完成。


小ma說不收,我就又跑了一次

上一篇,小ma好不容易學會:

Ready 與 Fanout 必須拿同一份 Plan 判。

這件事修好之後,我原本以為 Planning 這一段終於比較像正常工程流程了。

Plan 有 Authority。

Gate 會檢查。

不符合就拒收。

很好。

直到有一份 Planning Artifact 被擋下來。

小ma回了一句:

planning artifact is not accepted: <path>

我看著這句話幾秒。

「哪裡不接受?」

沒有下文。

「少哪一段?」

沒有。

「Planner 到底寫了什麼?」

也沒有。

這種時候,身為普通工程師的我,最自然的反應就是像家裡老人家一樣,關機,重開,再拍兩下電視。

於是我砍掉任務,重新 claim,再跑。

還是不收。

我再跑一次。

還是不收。

第四次,小ma依然很有原則:

「不收。」

理由則依然保持Unknown。

很好。

一套號稱有 Gate 的 Workflow,最後把我拒於門外四次,四次啊,真有原則。

老Go看了一眼紀錄。

「你現在是在 Debug,還是在抽卡?」

我沒有回答。

因為第四抽跟前三抽一模一樣。

至少抽卡偶爾還會歪出別的東西。


小bu說先繞過去,這次我差點又答應了

小bu對這種場面一向很有行動力。

「既然只是 Plan 被退,我可以先直接做。」

聽起來其實很合理。

Issue、Repo、目標檔案大致都知道。

Builder 也不是完全沒 Context。

先讓它開工,等做完再請小re Review,不就好了?

我腦中很快浮出一條便宜路徑:

Plan 被拒
→ 先人工看個大概
→ Builder 直接開始
→ Review 時再補洞

這條路我太熟了,它叫workaround。

Spec 不完整,先 Bring-up。

文件沒更新,先 Patch。

介面還沒定,兩邊先各自猜一版,晚點再說。

這真的能救急,直階,只是背後還要付出更多技術債。

例如,三天後大家開始爭論:

「你當初叫我做的,不就是這個嗎?」

而我會陷入一段很有修行感的內觀:

我當初到底叫它做了什麼?

小re沒有阻止小bu寫 Code。

它只問:

「被拒的是格式、缺少驗收條件,還是 Plan 裡本來就有一個沒決定的問題?」

「不知道。」

「那你現在要 Builder 繞過的是排版問題,還是尚未做出的工程決策?」

「……也不知道。」

這就麻煩了。

同一句 not accepted,背後可能是:

status-not-accepted
required-section-missing
blocking-decision

第一種可能只是狀態還沒進到可接受階段。

第二種是 Artifact 結構不完整。

第三種則代表 Plan 裡真的有一個 Decision 還懸著。

把三種情況都翻譯成「再跑一次」,跟看到所有 Kernel Error 都先 Reboot 差不多。

有時有效。

因為機器也只是衷實執行程式碼。

Target


群裡第一次有人不搶著做,而是先問 Builder 到底要做什麼

就在小bu準備繞過 Gate 時,群裡有個人沒有先開 Worktree。

它先問:

「這份工作完成時,Builder 應該交出什麼?」

我把 Issue 丟給它。

它又問:

「哪些 Decision 已經確定?哪些還要人決定?」

我把前面的討論也丟過去。

它繼續問:

「Reviewer 要拿什麼判斷完成?只看 Test,還是還有 Source、Runtime 或 Compatibility Boundary?」

老實說,問到第三題時,我已經有點想把它靜音。

我只是想讓 Agent 開工。

它卻像宗門任務堂那位特別難搞的執事,拿著玉簡逐條確認:

任務是什麼?
邊界在哪裡?
誰能做決定?
交付物是什麼?
什麼狀況算完成?

但煩歸煩,這些問題正好把前面的混亂照亮了。

小bu負責的是:

把已經定義好的東西做出來。

小re負責的是:

找出實作與證據裡的洞。

小ma負責的是:

哪個 Lifecycle Transition 還不能發生。

那麼,誰負責把一個模糊的「幫我處理這個 Issue」,整理成 Builder 真正可以接手的工作?

這個責任以前一直是我自己在扛。

只是我沒有替它命名。

現在它終於從群裡浮出來。

我叫它小pa,也可以叫他小趴,或是叫他小%。

正式角色叫 Planner。

它的偏見不是「我來修」。

而是:

「先把 Builder 到底要做什麼說清楚。」


Planner 不是幫 Prompt 寫長一點

我以前對 Planning 的理解,其實非常接近「把 Prompt 補完整」。

例如原本是:

修掉 Issue #511

補成:

請先讀 Issue、相關 Source、Tests 與 History,
整理 Root Cause,提出 Plan,再交給 Builder 實作。

字比較多。

看起來也比較專業。

但只要這些內容仍然活在某次 Session 裡,Planning 就還是一次性對話。

Session 關掉,誰都不確定當時接受的是哪一版。

Manager 也無法對它做明確 Gate。

Builder 只會拿到一段「大概是這個意思」的 Context。

真正能進 Workflow 的 Planning,必須留下 Artifact:

Requirement
→ Spec
→ Plan
→ Task
→ Acceptance Boundary

這份 Artifact 要能回答:

  • 要改什麼,不改什麼。

  • 哪些 Decision 已經確定。

  • 哪些條件仍然阻塞。

  • Builder 要交什麼。

  • Reviewer 拿什麼查。

  • Manager 何時可以放行下一步。

所以 Planner 的工作不是替 Agent 多寫幾句鼓勵。

它是在 Work 進入 Build 前,替後面所有角色建立同一份工作定義。

上一篇講的是 Plan Authority:

Ready 與 Fanout 必須信同一份 Plan。

今天又多了一層:

那份被大家共同信任的 Plan,本身也必須是一份可以被檢查、拒收與追查的 Artifact。

不然我們只是很有紀律地共同相信一段看不到的文字。

修真一點的說法,叫全宗同心。

工程一點的說法,叫 Shared Blind Spot。

Target


Gate 其實早就知道答案,只是 Manager 全部丟掉了

回頭看 Source,事情更尷尬。

Planning Gate 並不是只會回一個 True/False

assess_planning_artifact() 原本就會產出:

ArtifactAssessment
├── accepted
├── reasons
└── blocking_markers

也就是說,它知道:

  • 哪一類條件沒過。

  • 哪幾行有阻塞標記。

  • 原文大概寫了什麼。

問題出在 Manager 往上拋 Error 時,只拿了:

accepted == false

其他全部丟掉。

被拒的 Artifact 又只活在 TemporaryDirectory 裡。

Context 一結束,內容跟著消失。

所以系統內部其實發生的是:

Planner 寫出 Artifact
        ↓
Gate 看懂哪裡有問題
        ↓
Manager 把理由丟掉
        ↓
Temporary Directory 把原文刪掉
        ↓
Operator 收到:不接受

這條流程很好,分工完美,每一層都知道一點,大家都丟一點,最後我什麼都不知道。

小ma的「還沒」原本應該是 Lifecycle Discipline。

到了這裡卻變成一位只會蓋退件章、不附審查意見的長老。

你可以尊重它的權威。

但你很難因此進步。

Target


PR #513 沒有放寬 Gate,只是讓退件終於能被 Debug

這次沒有把 Planning Gate 改鬆,也沒有遇到拒收就自動放行。

相反地,拒收仍然照舊 Raise。

只是 Error Message 不再只剩 Path,而是帶上有界資訊:

reasons
→ markers
→ evidence path

之所以要有界,是因為這段 Reason 還會流進 needs_human、Control Contract 與 Recovery Path。

不能把整份 Plan 原文塞進一行 Status。

因此訊息限制單行、總長度最多 400,Marker 最多帶幾條截斷後文字。

完整內容則另外寫成:

cortex-planning-artifact-rejection/v1

放在 Coordinator Root 的 Evidence 目錄,保留:

kind
path
content
reasons
markers
work_id
run_id
created_at

內容過長時會截斷並留下原始長度。

Evidence 採原子寫入。

至於 Coordinator 是誰,我們下章分曉。

但如果 Evidence 寫入本身失敗,仍然不能把真正的 Planning Rejection 換成一個 IO Error。

所以這條旁路是 fail-open:

Evidence 寫失敗
→ 留 Log
→ 原本的拒收照常成立

這個取捨很重要。

診斷是為了讓 Failure 可理解。

不能因為診斷工具自己跌倒,就把原本的 Failure 身分洗掉。

否則下一次我看到的會從:

Plan 不接受。

升級成:

寫退件紀錄失敗。

至於 Plan 到底怎麼了?

恭喜,再抽一次。


今天證明的是「退件可診斷」,不是 Planner 從此不會寫錯

PR #513 新增 14 條 Regression Tests,完整結果為:

2517 passed
49 subtests passed
policy check exit 0

它驗證了:

  • 三類 reasons 會進入訊息。

  • blocking-decision 的行號與文字能被看到。

  • 訊息保持單行且有長度上限。

  • 被拒 Artifact 內容會落到 Coordinator Root。

  • 超長內容會留下 Truncated Metadata。

  • Evidence 寫入失敗不會蓋掉真正的拒收原因。

  • 成功路徑不會平白多寫一份 Rejection Evidence。

這些能支持的主張是:

Planning Artifact 被拒時,Operator 終於能知道為什麼被拒,也能回頭看 Planner 寫了什麼。

它不能支持:

Planner 從此不會產生爛 Plan。

也不能支持:

所有 Blocking Decision 都已經能在 CLI 裡直接回答,自動 Recovery 也已完整打通。

Issue #511 還有後續項目。

今天只是先把最荒謬的一段補起來:

系統明明知道退件理由,卻叫人類靠重跑猜答案。

小pa現在終於可以把一份看得見的 Plan 放到桌上。

小re可以指出哪一段不成立。

小ma可以說哪個 Transition 還不能過。

小bu則已經在旁邊等很久了。

它看著被拆好的十個 Task,舉手:

「所以我先拿哪一個?」

另外九個小bu也同時舉手。

群裡瞬間像宗門發放限量功法。

Plan 已經說清楚了。

下一個問題是,誰來決定這些工作怎麼排、怎麼組、哪個先拿、哪個必須等前一個完成?

下一篇,Planner 把工作拆完,十個 Agent 一起伸手。

Have a nice day.


上一篇
Day 18 - 十個小工都準備好了,Canary 卻走不出去:跨 Repo 時到底該信哪一份 Plan?
下一篇
Day 20 - 明明有skill,怎麼還老是跑歪?:把skill寫成牌組 - DECK
系列文
一天一個 PR,讓我告訴你嵌入式開發的殘酷:如何為 AI Agent 建立可信的工程閉環21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言