AI Workflow · part 10
[Dev Workflow] 交辦 AI 工程任務:交出去的單位是一張工單檔,不是一段對話
❯ cat --toc
TL;DR
交辦 AI 工程任務,交出去的不是一段對話,是一個檔。我把工程執行單寫成自足的工單(目標/步驟/機器可驗收/紅線),detached 丟給 Codex 跑,報告收到先過閘門再信。用死時間換來的鐵律:背景 worker 卡死時不會叫你——一次 codex exec 躺了 2 小時 17 分,其實 task_started 後約 5 秒就死、CPU 貼零。三道疤變三個 flag(harness 內不 nohup &、-c "mcp_servers={}"、< /dev/null),外加一個約 5 分鐘的看門狗。而且報告只是 claim:信之前,自己把驗收重跑一遍。
白話版:把工程任務外包給 AI
把工程任務外包給 AI,有兩種交法。一種是它趴在你桌邊,你邊講它邊做——你講一句、它問一句,你得一直坐在那。另一種是你遞給它一張工單:上面寫清楚要做什麼、怎麼樣算做完、哪些東西碰都不准碰;它自己拿去做,你事後驗收。
我原本用第一種,後來全改成第二種。原因很簡單:第一種只有你坐在旁邊時才行,而外包的整個重點,就是你想走開去做別的。
第二種的關鍵,是那張工單要「自己會說話」——連一個完全沒聽過這件事的人,光看單子就能做對。做完算不算數,不能是「我覺得好了」,得是機器能自己核對的東西:某個指令跑完沒報錯、某個檔案裡確實有那一行字。
但這裡有個很反直覺的坑:AI 不會主動告訴你它卡住了。它當機的樣子,跟它正在埋頭苦幹,長得一模一樣——畫面就是靜靜的,你以為它在做,其實它早就死了。所以你得自己裝一個「它還活著嗎」的鬧鐘,每隔幾分鐘去戳一下:還有在動嗎?沒動,就是卡死了,不是做得慢。
這篇講的就是:怎麼把活寫成一張自足的工單、怎麼安全地丟出去、以及為什麼「收到完工報告」跟「真的做完了」是兩回事。
前言
派一張工單給 Codex,我就轉頭去忙別的。2 小時 17 分後回來看,它還「在跑」。其實它沒在跑——task_started 之後大概 5 秒就卡死了:CPU 貼著零、session log 凍在兩行(我丟的 prompt,加一行 task_started),之後再沒長過。我會回頭去看它,純粹是心裡飄過一句「不會這麼慢吧」。
當機你會知道:它會退出、印一段 stack、總有什麼把你叫醒。但背景 worker 的卡死是靜悄悄的——它躺在那的樣子,跟它正在幹活一模一樣。就是那個下午之後,我不再「用講的」把任務交給 AI executor,我改成遞它一個檔。
這是 AI Workflow 系列 的第十篇。上一篇 外化的是 live state——做到哪、背景在跑什麼——好讓新開的 session 能把任務接回去。這篇把時間點再往前挪一格,外化的是任務本身。跟 publish-gate hook 同一條路子:把紀律從我腦袋裡搬出去,搬到機器管得動的地方。
交辦的單位是一個檔,不是一段對話
一開始把活丟給 Codex,我用的是最直覺的方法:在對話裡把事情講一遍,就像同事湊過來趴在你桌邊、你順口交代那樣。我坐在那接它追問的時候,這招很好用。但只要我想走開——而走開正是交辦的全部意義——它就散了。
對話很不適合拿來交辦,三個原因。一,它不自足:一半的 context 埋在 executor 看不到的 scrollback 裡。二,它沒有驗收條件:「把它弄好」這種話,一個 process 沒辦法拿來自己核對。三,它跟 session 一起死:視窗一關,任務就再也撿不回來。
所以我給自己的第一條規則,擺在任何派工「之前」。先問一句:這是不是一張工程執行單?——建環境、資料 pipeline、一批機械式的改檔、build 的雜活,凡是驗收能靠機器判定的都算。是,就寫成工單交出去;是判斷、是品味、是一次性的決定,就留在我這。(有個誠實的例外:一行 config 改完、幾秒就能驗、幾秒就能滾回來的,不值得開單——寫單比改本身還貴。門檻是「有沒有調查量、有沒有跨檔邏輯」,不是改幾行。)
一句話:指揮官不下場。主 session 負責切單、仲裁、收攏;真正動手實作的部分,交給別處、對著一個檔做。
一張工單就四塊:目標、步驟、驗收、紅線
工單長什麼樣?就四塊:
- 目標:什麼算做完,一兩行講清楚。
- 步驟:要改什麼,編號列出來。
- 驗收:機器能自己判定的檢查——exit 0、grep 回空、某個檔案存在而且裡面有 X。
- 紅線:什麼不准碰,而且一定包含一句「世界跟工單對不上就 STOP,回報別硬做」。

四塊裡最容易被略過、也最不能略過的,是驗收那塊——它讓這張單子不靠我在旁邊,也驗得出對錯。這塊後面單獨拆。
一張真實工單長怎樣:退役類,危險在做太多
舉一張遮掉敏感資訊的真工單。這種「退役」工單特別危險,因為它的失敗模式不是做太少,是做太多——手一滑多刪一個東西,就回不來了。
- 目標(Goal):退役一個 legacy 的 mirror hook,active config 裡不該再有任何東西呼叫它。
- 步驟(Steps):從 hooks.json 移掉那個 mirror hook;停掉會觸發它的 launchd job;四份 archive 一份都別動。
- 驗收(Acceptance,機器判定):
jq empty ~/.config/<tool>/hooks.json要 exit 0(JSON 沒被改壞);grep -rl mirror-memory.sh <active-configs>/要回空(active 端真的沒人再引用);四個 archive 目錄全都還在。 - 紅線(Red lines):不刪 archive;不直接改 runtime 那份;拓撲跟工單對不上——比方 active config 長得跟我描述的不一樣——就停下回報,不要自己腦補繼續。
注意驗收那三條:每一條都是一個能自己跑、自己回答是非的命令。不是「確認 hook 移乾淨了」這種要用眼睛看、要用判斷的句子。
最吃重的是驗收那塊:寫不出 shell 能判定的,就是還沒想清楚
四塊裡最吃重的是驗收。因為「做好了」這三個字沒辦法核對,但 jq empty … exit 0、grep 回空可以。
這裡有個對我很有用的自我測謊:如果我寫不出一條 shell 能判定的驗收,那不是 executor 太笨,是這張工單還沒 ready——真正沒想清楚的是我,不是它。驗收寫不出來,通常代表我自己都還沒把「什麼算做完」想到能落地的程度。這是我這邊要解的問題,不該丟給執行者扛。
菜雞測試:一個沒有記憶的 AI 憑這張單能不能一次做對
工單寫完,我只有一個自驗方法,叫它菜雞測試:一個完全沒有這段對話記憶的 fresh AI,光憑這張工單、加上我附的那幾個檔,別無其他,能不能第一次就做對?不能,就是我 under-spec 了。
血的教訓我付過。有次我派了一張菜雞級的窄工單:只寫了步驟,沒講這件事在整個專案裡的位置,也沒提環境裡那幾顆雷。executor 一頭撞進 ssh / tmux / detach 的邊界,自己推不動、卡死兩次、來回問了我五輪才做完。那不是 executor 弱,是工單爛。派工的品質是 orchestrator 的責任,不是執行者的——這點我得一直提醒自己。
派工是背景作業,不是前景對話
工單寫好了,發射方式是 detached——丟出去在背景跑,不是開一段前景對話陪它。在 harness 裡我用它內建的背景執行機制:跑完會回頭叫醒我、讓我收報告。
命令長這樣,每一個 flag 都是一道疤換來的:
codex exec -C <repo> --dangerously-bypass-approvals-and-sandbox \
-c "mcp_servers={}" \
"$(cat ~/tickets/<ticket>.md)" \
> ~/logs/<name>.log 2>&1 < /dev/null
那些 flag 為什麼長這樣,下一節一個一個講。
失敗博物館:三面用死時間換來的旗標
上面那行命令裡的每個 flag,都是我用一段回不來的時間換的。

一、harness 裡不准 nohup … &。 在 harness 裡用 nohup,會讓 process 脫離 harness——跑完不會回頭叫醒你,報告就飄在 log 檔裡沒人收,而你以為它還在做。這個坑我踩了三次才記住。講清楚:裸終端裡有人盯著,nohup 沒問題;這條規則專指「跑在一個本該叫醒你的東西裡面」的時候。
二、-c "mcp_servers={}" 一定要帶。 exec 工單只需要一個 shell,載 MCP 純粹是找風險——transport 一啟動失敗(OAuth 沒 token、跳 AuthRequired),就能讓整個 process 在 task_started 之後永久卡死。開頭那具躺了 2 小時 17 分的屍體,根因就是這個。餵它一份空的 MCP config,這一整類卡死就消失了——一張 exec 工單本來就沒理由去載 MCP。(它清掉的是你自己設定的 servers,擋不掉某些 host 或 plugin 硬塞進來的 MCP,所以是很強的預設,不是絕對保證。)
三、< /dev/null 一定要帶。 codex exec 的 stdin 不是 TTY,沒接個管道給它,它會 block——傻等一個永遠不會來的輸入。把 stdin 導去 /dev/null,它就等不了了。
看門狗:那個不是 flag 的 flag
還有一個東西不是 flag,但比 flag 更關鍵:看門狗。
卡死的 run 永遠不會退出 → harness 那套「做完叫醒你」就永遠不會觸發 → 於是這套背景機制唯一抓不到的,就是「永遠不完成」這種狀態。它能回報成功、回報失敗,唯獨回報不了「其實早就死在原地」。
所以每張工單旁邊,我都掛一個大約 5 分鐘一次的 log 新鮮度檢查。卡死的指紋其實很乾淨:CPU 貼著零,但 elapsed 已經幾十分鐘;session log 凍在一兩個事件不動;log 檔不再長大。沉默不等於進度。一個沒有 liveness 訊號的背景 worker 是在賭博,那 2 小時 17 分就是賭輸的價碼。
報告不等於結果,收到先進閘門
executor 回一份「完工報告」,不等於任務做完了。產出者不該自己去驗那份高風險的修改。所以報告一收到,先進閘門,我做四件事:
一,抽驗一條紅線。它說沒碰 archive?那我自己 ls 一遍那四個目錄。
二,逐條重跑驗收。不是讀它那句「全部完成」的總結,是真的去跑 jq、真的去 grep,對著系統的真實狀態跑。
三,掃它跟我既有認知的衝突。報告要是跟我的筆記打架,那反而是很有用的訊號:可能筆記過時了,也可能報告在幻覺——查清楚才裁,不選邊站。
四,實物抽查一項。檔案真的在?測試真的跑過?change 真的 live 在跑的系統上,不只是 repo 綠?
裁決只有三種:go、附上失敗軌跡重派、升級問人。這也呼應上一篇:別靠一面之詞就繼承「它會動」——往上疊之前,先把最後一步重跑一遍。
講一個具體的危害。背景 agent 撞到 session limit、在半路回報,特別危險。我開過一個標著「完工」的模組,一看,函式回三個值,它唯一的 caller 還在解兩個——任何走得到的成功路徑,都會炸 ValueError。報告說 done,驗收說不是。就是這道縫,撐起了閘門存在的理由。
收穫
最花時間的地方
不是寫工單。格式變成肌肉記憶之後,寫一張很快。真正吃掉時間的是卡死——單單一個死掉的 run 就是 2 小時 17 分(那時看門狗還沒出現),外加另一張 under-spec 的工單卡兩次、來回五輪。兩筆都是「靜默」成本:失敗得很安靜的背景 worker,用一段你根本沒察覺自己在花的 wall-clock,悄悄跟你收費。
可搬走的診斷
liveness 訊號 ≠ completion 訊號。前者你得自己另外做,因為後者在 process 卡死的那一刻,只會一直沒來、用沉默騙你。能用機器檢查的驗收,是任何派工的基石——寫不出檢查,代表你還沒真的搞懂這個任務,那就還不該把它交出去。報告是 claim,驗收重跑才是 evidence。
通用原則
指揮官不下場:把任務寫成一個不靠你也能驗的檔,派出去,收到報告先卡關重驗——跟上一篇把 live state 寫出來是同一招,只是這次更早,在派工前就做。
發射前的 checklist
- 這是工程執行單、而且驗收能靠機器判定嗎?不是,就自己留著做。
- 四塊齊了嗎:目標、步驟、驗收(shell 能判)、紅線。
- 菜雞測試過了嗎——一個沒記憶的 AI 憑這張單能一次做對?
- detached 發射,帶上
-c "mcp_servers={}"和< /dev/null;harness 裡不要nohup &。 - 掛一個大約 5 分鐘的看門狗——卡死不會自己回報。
- 收到報告 ≠ 做完:重跑驗收、抽驗一條紅線,再裁 go / 重派 / 升級。
常見問題
- 怎麼把一個工程任務交給 Codex 這類 AI executor?
- 別用對話交,用一個自足的工單檔交。四塊:目標(什麼算做完)、步驟、機器可驗收的驗收條件(exit 0、grep 回空、某檔存在且含 X)、紅線(什麼不准碰)。判準是菜雞測試——一個沒有你這段對話記憶的 fresh AI,光憑這張單加你附的檔能不能第一次就做對?不能,就是你 under-spec,那是你要修的,不是 executor 的能力問題。
- 為什麼背景跑的 codex exec 會永久卡死?
- 常見兩個原因。一是 MCP transport 啟動失敗(少一個 OAuth token、跳 AuthRequired),整個 process 在 task_started 之後就掛在那——所以 exec 工單一律帶 `-c "mcp_servers={}"`,根本不載 MCP。二是非 TTY 的 stdin 沒接管道會 block,一律 `< /dev/null` 導掉。卡死的 run 永遠不會退出、也就永遠不會通知你,所以要另外掛一個約 5 分鐘的 log 新鮮度看門狗。
- 收到 AI 的完工報告,等於任務做完了嗎?
- 不等於。產出者不該去驗自己那份高風險的工。把報告當成一個 claim,不是結果:自己把工單的驗收條件重跑一遍、抽驗一條它宣稱沒碰的紅線,再實物抽查一項,才算數。報告說的是「我覺得可以」,驗收跑出來的才是「到底行不行」。
- 什麼工作該丟給背景 executor,什麼該留在主 session?
- 用一句話分流:這是不是一張驗收能靠機器判定的工程執行單——建環境、資料 pipeline、批次改檔、build 雜活?是,就寫成工單交出去。判斷、品味、一次性的決定,留在 orchestrator 手上。至於一行 config、幾秒就能驗又能滾回來的,不值得開單——寫單比改還貴,直接做完自己確認就好。
接著讀
- 2026-07-11[Dev Workflow] 額度斷在半路:讓 Claude Code 跟 Codex 交接任務的 handoff 協議
額度斷在任務半路,蒸發的不是知識庫,是這個任務的 live state:做到哪、排除了哪些死路、下一步跑哪個命令。這篇講我怎麼把 state 外部化成一個 handoff 檔,讓 Claude Code 跟 Codex 經 symlink 讀寫同一份、互相接手彼此做一半的活——以及用 hook 上鎖時踩到、又修掉的一個 false-takeover bug。
- 2026-07-12[Dev Workflow] 退役 AI agent 的記憶系統:doctrine 跟 runtime 漂開時的三個暗坑
退役一個 AI agent 的舊記憶系統,看起來只是半小時的雜活,結果連踩三個暗坑:退役的資料夾其實是現役鏡像、doctrine 說有的備份根本不存在、hub-and-spoke 拓撲讓你從單台機器只看得到半張圖。根源都同一個——白紙寫的 doctrine 早跟實際在跑的 runtime 漂開了。
- 2026-02-26[Dev Workflow] 讓兩個 AI 吵架。它們不同意的地方才是重點。
一個 /debate 指令讓 Codex CLI 和 Gemini CLI 對同一份程式碼互相辯論。訓練資料不同、盲點不同——它們的分歧通常是最有用的輸出。
- 2026-05-19[Claude Code] 規則我會跳過,hook 我跳不過 — 我替自己裝了一個發文閘
我有一條「發文前要查證」的規則,還是寫了三次說錯。問題不在規則,在它放在哪一層。這篇講我怎麼把它從 skill 升級成 hook — 一段守在「按下送出」那一刻的小程式,沒查證連嘗試都不准。
不想錯過新文章?
訂閱我確保不漏接!
隨時一鍵退訂。