🛑 停止畫面、停止工作、擋住舊結果、處理已發生的效果,是四件要分開確認的事。
昨天在 Day 5|訊息先到了,模型還沒讀到:收下和送達要分開記,我們分清楚「使用者的話被收下」和「真的送到模型面前」。模型還在忙,新訊息可以先排隊。
快速回顧一下:Run 是一次任務的執行;Harness 是負責跑 loop 的那一層,讓模型決定下一步、執行工具,再把結果交回模型。
今天接著問:如果使用者按的是停止,也要等模型忙完才處理嗎?
先看同一個事故。使用者請 Agent「把 timeout 調成 30 秒」,這次任務叫 Run A。模型選錯了目錄,Harness 卻已經啟動 shell,準備逐一修改裡面的三個設定檔。使用者發現不對,按下 Stop;畫面停止輸出,磁碟上的檔案卻還在變。
原因並不神祕:如果 Stop 只讓前端停止顯示,或只讓 Harness 不再等待回覆,已經啟動的 shell 不會因此知道自己該停。Harness 還得把停止要求送到負責執行 shell 的程式,再確認它是否結束。
即使 shell 停下來了,也可能已經改完前兩個檔案。取消能要求後續工作停下來,不能讓已完成的寫入自動消失。
今天先分清楚「停一下」或中途補話有哪些處理方式,再看 Codex 和 Claude Code 按下停止各停了什麼、平台與框架的 Interrupt 又是哪一種。接著回到 Run A,把取消拆成四層確認,說明實作時各要接在哪裡,最後再判斷畫面何時才能說「已取消」。

圖:同一次取消裡,畫面停止、shell 結束、結果送回是不同時間點;最後還要查磁碟上留下了哪些變更。
Run A 還在跑時,使用者可能補話、要求等一下,也可能要求整次取消。這些意圖不同,Harness 不該全部當成「再送一句話給模型」。
下面是我用來比較產品行為的六種語意,不是業界共同標準,也不是互斥、完整的分類法。斜線連接的是相關動作,不表示兩個詞在每套框架都能互換。
| 我用來比較的語意 | 放回 Run A,使用者是在要求什麼? | 現有工作如何處理? |
|---|---|---|
| Queue(排隊) | 「先做你的,這句晚點處理。」 | 先收下輸入,等指定時機再送達;可能是工具結束,也可能是整輪結束。 |
| Steer(調整方向) | 「保留可用進度,接下來只看 services/api。」 | 讓執行中的流程在可接收新輸入的位置改變後續方向;不保證打斷當下的 shell。 |
| Pause / Resume(暫停/恢復) | 「先等我確認目錄,之後還要接著做。」 | 在支援的暫停點保存進度,等回答後恢復。 |
| Cancel / Abort(取消/中止) | 「Run A 不要再做下去了。」 | 結束指定的這次執行;在途工作和已發生的效果還是要另外處理。 |
| Rollback / Restart(回退/重新開始) | 「捨棄這次進度,從先前狀態重新來。」 | 在支援的範圍內回退,再啟動新執行;單純重跑並不包含回退。 |
| Reject(拒收) | 使用者送了新要求,但系統回答「目前忙碌,這筆不接受」。 | 原工作繼續,新要求不進等待清單。 |
這張表混合了兩個問題:新要求何時處理,以及舊執行要留到什麼程度。 因此它們可以組合。例如,新訊息先排隊,工具結束後拿來調整同一輪,就是先 Queue、再 Steer;中止舊執行後帶著新輸入重跑,也同時包含 Cancel 和 Restart。
Pause 和 Cancel 的差別,則在於系統是否打算保存一個明確的恢復位置。Pause 是「等回答後繼續」;我這裡說的 Cancel 是「這次執行到此為止」。取消後還可以另開工作,讀取之前的紀錄,但那不表示被中止的 shell 會從原本那一行接著跑。
標題裡的 Interrupt 特別容易誤會。它只看名字不足以判斷是暫停、取消,還是中止後立即接新輸入。下面幾個官方實例,剛好用了同一個字,卻有不同效果。
先對齊範圍:我用 Run 表示一次任務的執行;Codex 的 turn 是一段回合執行,裡面可以有多次模型與工具呼叫。
一般執行畫面按 Esc,走的是我這裡說的 Cancel / Abort,範圍是目前 turn。 它不是讓 shell 暫停、等一下原地續跑,也不能擴大成整段對話和所有背景工作都取消。
下文的 thread 指同一段持續保留的對話。
以下依 2026-09-20 查到的 Codex main,commit 5c5308f。情境限定為預設按鍵、一般執行畫面,沒有彈窗或編輯模式先接走 Esc。source 的按鍵設定把 Esc 對到中止目前 turn;Core 的操作定義則明寫,這不會終止背景 terminal processes。按鍵設定、操作定義。
| 操作 | 目前 source 的處理 | 它沒有承諾什麼 |
|---|---|---|
| 一般執行畫面按 Esc | 送出中止要求,Core 結束目前 turn,回報 TurnAborted。 | 不因此終止背景 terminal processes,也不復原已修改的檔案。 |
| /ps | 列出該 thread 管理的背景 terminals。 | 列表只是觀察,不會停止工作。 |
| /stop | 走獨立的背景 terminal 清理操作,要求終止該 thread 管理的背景 processes。 | 不是 Esc 的別名,也不是停止機器上或遠端所有工作的總開關。 |
這裡的背景 terminal,是 Codex 管理、可持續執行 shell 指令的工作,不是使用者另外開的每個終端機視窗。
回到 Run A:如果改檔指令還留在這類 terminal 裡,Esc 結束 turn 後,它還是可能繼續寫檔。 要停這些工作,需另外使用背景 terminal 的停止操作;已寫進去的內容則還要檢查。
這也有實際執行路徑:終端機介面(TUI)收到 /stop,會把要求交給核心執行程式(Core),再由它通知管理中的背景 process 終止。程式本身也提醒,中斷的工具可能已部分執行。TUI 指令分派、Core 處理、process manager、中止後提醒。
Codex 也支援另一條路:執行中接受新輸入,用來調整目前 turn。這才接近我這裡說的 Steer;Core 提供的 StartOrSteer 就是閒置時開始新 turn、有 active turn 時調整它。接受輸入還是不等於模型已經讀到,這點延續 Day 5。如果原本還有待送輸入,中止後也可能開始後續工作,因此 Esc 不能解讀成永久關閉這個 thread。輸入語意、中止與後續工作。
Claude Code 的官方互動文件說明:一般執行情境下,Esc 會中斷目前 response 或 tool call,讓使用者重新指示;先前已完成的工作保留。如果有排隊訊息,會接著送出。因此它接近「中止目前執行,再視新輸入繼續」,不能推成整個 session 不再動作。有對話框時,Esc 也可能只是關閉對話框;在權限提示上則是拒絕那個動作。
Run A 已經用 shell 改了兩個檔案,Esc 不會把它們恢復。要回到先前狀態,Claude Code 另有 rewind:輸入框為空時連按兩次 Esc 可開啟選單,也可用 /rewind。而checkpointing 文件特別指出,Bash 指令造成的檔案變更不在其追蹤、復原範圍。
停止和復原,是兩個不同操作。
接下來從操作產品,換成開發者怎麼接流程:先看 LangSmith 如何處理「工作還在跑,又收到新要求」,再看 LangGraph 與 OpenAI Agents SDK 如何做出「動手前先等人」的等待點。先等人核准,和動手後要求停下來,是兩件事。 後兩者是拿來建構 Harness 的框架或程式庫;看的是開發者要接上的控制方式。
LangSmith 是提供 Agent 追蹤、評估與部署的平台;這裡用到的 Deployment/Agent Server,負責把 Agent 流程當成服務執行。 當服務正在處理第一個要求,使用者又送來第二個,就得決定讓誰繼續。官方把這種情況稱為 double texting;下面這組處理策略是部署服務的功能,不能直接當成開源 LangGraph 自帶的同一套機制。平台與框架的分工。
官方文件列出四個選項:
所以這裡的 Interrupt 不只是「停」,還決定了「接下來用哪些進度繼續」。Rollback 退的是框架管理的狀態;不能據此認定 Run A 已寫入磁碟的檔案也一起復原。外部效果還是要另查。
LangGraph 是給開發者建立 Agent 流程的框架。 你把工作拆成步驟,寫出各步驟要做的事,以及完成後走向哪一步;它負責依這個流程執行與保存進度。步驟可以是普通程式,也可以呼叫模型。它能用來建構自己的 Harness,並不是安裝後就能直接幫你改程式的聊天產品。官方介紹。
用 Run A 的需求來做,假設我們希望「先讓人確認目錄,再改 timeout」,可以安排成:
interrupt() 把方案交回應用程式。應用程式顯示確認畫面,流程停在這裡。要讓等待後接得回來,需要配置 checkpointer(保存流程進度的元件),再用同一個 thread ID 找回同一份進度。應用程式再把使用者的回答傳回,流程才繼續。因此你可以做出「先產生修改方案,等人稍後確認,再接著執行」的功能;如果要跨 process 等待,進度就要保存到資料庫等持久位置,不能只放記憶體。Interrupts 文件。
有一個會影響程式怎麼拆的細節:恢復時,包含 interrupt 的那個步驟會從函式開頭重跑。 因此上例把產生方案、等待確認、寫入拆開;等待步驟只讀已保存的方案。不要在同一個函式裡先寫檔再等待,否則恢復時,等待之前的寫入可能再做一次。
你從這個例子可以帶走的是:等待人工確認,必須有保存的進度和明確的後續分支。 單純顯示一句「要繼續嗎?」並不會讓正在執行的流程自動停住。
OpenAI Agents SDK 是建立 Agent 的程式庫。 你提供指示、模型和工具函式,它提供執行迴圈,負責呼叫模型、執行工具,再把結果帶回去。今天要看的是它的 human-in-the-loop(HITL,人工介入)功能:讓需要核准的工具,在動手前先等人的決定。SDK 介紹。
同樣要改 timeout,可以把「修改指定目錄的 timeout」做成一個工具,再標記 needs_approval,要求執行前核准:
如果使用者不會立刻回答,可把暫停進度轉成 RunState(可保存、恢復的執行狀態),存好後稍後載入;不必為了等核准一直維持原本那個函式呼叫。HITL 文件。
這樣可以做出「Agent 先準備修改,使用者確認後才寫入」的操作。拒絕的是這次工具呼叫,整個任務還是可能繼續回報或提出其他方案;而已經啟動的 shell,不會因為另有一筆工具等待核准就自動停下來。
兩個例子都屬於 Pause / Resume:LangGraph 讓你安排流程中的等待點與分支;這個 Agents SDK 範例則利用工具的核准設定,在指定呼叫前等待。它們都把確認放在寫入之前。如果 Run A 的 shell 已經開始改檔,就要回到下面的 Cancel 四層處理,不能用「等待核准」代替取消。

圖:這是兩個範例希望實現的共同操作。核准與拒絕的分支必須接到真正的執行流程;等待期間,圖中的這次寫入尚未開始。
現在使用者的意思已經明確:目錄錯了,Run A 不要繼續。以下四層是我用來檢查設計的四個面向;前面的產品不一定用同一個操作涵蓋全部。
Run A 已經啟動的 shell 還在跑,但 Harness 應先禁止 Run A 再派出下一個模型請求、工具呼叫或子任務。模型即使剛好回了「接著跑測試」,也不能照做。
後文說的「取消被接受」,是控制程式已確認要取消 A,也使這次執行失去繼續派工和提交結果的資格。前端只回一句「已收到」,還不算這一步完成。
這就是 Day 5 的一般訊息和取消要求要分開的地方。補話可以等工具結束再送到模型;取消要由模型外的控制程式處理,不必等模型讀懂「請停止」。
否則 shell 還要跑三十秒,你也得等三十秒才開始取消。
禁止的是繼續完成原任務;停止的動作、查證結果、清理暫存檔等取消收尾,還是需要執行。
不再派工,擋得住下一個工具,擋不住已經啟動的 shell。
Harness 還要通知真正執行指令的那一端停止,再取得結束狀態。
在 Run A 裡,理想情況是 shell 收到要求,停止修改後續檔案。但它也可能來不及停、無法被控制,或已經把工作交給另一台機器。
因此「停止要求送出」和「工作已結束」要分開記。
名稱只能當入口,還要查它停的是什麼。 AutoGen Core 是用訊息串接 Agent 的框架;它的 0.5.5 文件以本地 SingleThreadedAgentRuntime 為例,明寫 stop() 會立即返回,卻不會取消正在執行的 handler(處理訊息的函式)。另一個 stop_when_idle() 則會等到沒有待處理訊息、也沒有 handler 還在執行,才返回;它是在等工作處理完,不是強制中止那些 handler。這個例子提醒我:呼叫 stop 之後,不能只看方法名稱,就認定 Run A 裡正在改檔的工作也停了。AutoGen Core 0.5.5|Agent and Agent Runtime。
假設使用者取消 A,接著送出 Run B:「先只檢查 services/api,不要寫入。」A 的 shell 停下後,過了幾秒才回報「前兩個設定檔已修改,第三個未處理」。這份輸出可能早已產生,只是現在才送回 Harness。
這份回報屬於 A,不能直接拿來宣布 B 已完成,也不能讓 A 依原計畫繼續派出測試。可以保存它,作為查證 A 做過什麼的證據;但不能讓已取消的執行繼續改寫目前任務的進度。
這層必須在取消被接受時就生效,不能等第二層證明 shell 已停才開始擋。
否則等待停止的幾秒鐘,晚到結果就可能先被採用了。
接著核對 A 的回報。shell 確實已停,磁碟上的第一、第二個檔案已修改,第三個還沒動。這些已對環境造成的變更,就是 side effects(副作用)。
現在要決定的是:保留這兩個變更、恢復原內容,還是先請人確認。這不是前面三層能代替的事。特別是第三層只擋「結果被採用」,不會攔下 shell 直接對磁碟的寫入。
所以前面收到 B 的要求後,如果 A 是否停止還沒確認,就應先讓 B 等待,或讓 B 使用隔離且已確認的檔案版本。否則即使 B 只是讀檔,也可能讀到 A 正在改的一半內容;讓 B 接著寫同一批檔案,更可能互相覆蓋。丟掉 A 的回報,解決不了這種直接寫入的衝突。
四層是四個問題,不是一定要依序等待完成的流水線。
取消被接受時,就要禁止新派工、使舊執行失效,也把停止要求送下去;確認工作結束與清點變更,則可能需要更多時間。

圖:第一層擋新派工,第二層處理在途工作,第三層擋舊結果,第四層處理已發生的變更。檢查其中一層,不能代替另外三層。
知道各層在解什麼問題後,再看幾個常見做法。以下是實作建議,不是每套 SDK 都要求照抄的欄位或狀態名稱。
派出前與提交時,都要核對執行資格。這一節同時對應第一層「不再派新工作」與第三層「舊結果不得生效」。
要擋住 A 的回報,Harness 先得知道回報來自誰。每次派出工作時帶上 run_id,回來時核對來源和該 Run 的狀態:A 已取消,就不能再以 A 的名義推進任務。只看「session 還在不在」不夠,因為同一段對話可能已經在處理 B。
「有效」指是否還有權派工、提交結果,不是「哪個 Run 的時間戳最新」。如果系統允許多個 Run 同時執行,就分別核對各自的狀態與資源權限。
同一個 Run 如果會換 worker(跑這個 loop 的 process)接手,光有 run_id 又分不出新舊執行者。要判斷舊 worker 還能不能提交,就得知道哪一版執行權還有效。這和 Day 4 的 Attempt 不同:Attempt 記的是某一步重試了幾次。我把三種識別放在一起比較:
| 識別 | 它分辨什麼 | 什麼時候才需要 |
|---|---|---|
run_id |
是哪次任務的執行,例如 Run A 或 Run B。 | 派工與收結果時,用來找對 Run,再核對它是否還有效。 |
| Attempt(Day 4 的步驟嘗試) | Run 裡某一步的第幾次嘗試,例如同一個寫檔步驟失敗後重試了幾次。 | 要看某一步試過幾次、前幾次怎麼失敗時。它記的是步驟重試,無法判斷執行權是否還有效,單靠它擋不住已取消的 Run A 晚到提交。 |
generation(遞增版本) |
目前有效的是哪一版執行權,例如只接受版本 2,版本 1 的回報不能再提交。 | 同一 Run 換 worker 接手後,需要分辨新舊執行權、拒絕舊 worker 提交時。 |
Attempt 和 generation 解的是不同問題,不是二選一。
Attempt 留下步驟重試紀錄;generation 這類版本則用來區分新舊執行權。假設 A 在取消前曾換過 worker,已由持有版本 2 的 worker 接手;當時即使 A 還在跑,舊 worker 帶版本 1 回來也不能提交。版本必須隨工作傳遞,再由接收端核對,才會真的擋住舊執行。
如果 A 整個 Run 已取消,接收端核對 Run 狀態就應拒絕它繼續提交;不論 Attempt 或 generation 是多少,都不能讓 A 恢復執行。不必為了取消一律新增名叫 generation 的欄位,但如果要區分同一 Run 內的新舊執行權,就需要版本或等效機制,不能拿 Attempt 的重試次數代替。一步失敗重試還是屬於同一 Run;整件事失敗要從頭重做,則依 Day 4 另開新 Run。
取消與收結果還可能同時發生。
接收端先查「A 還有效」,準備寫入時取消才生效,分開的檢查還是會漏掉。核對資格和更新進度,要與取消操作協調先後。例如用帶有「A 還有效」條件的資料庫更新,讓取消與結果提交共同檢查同一份狀態;不能以為把兩行程式包進 transaction 就一定夠。派工端也要做相同協調:取消先生效,就不能再派;已先派出的工作,則歸第二層追蹤停止。
這延續 Day 5「只有 loop 寫模型對話」的做法:工具把結果交回,由統一入口決定是否寫入;控制程式可以先使 Run 失效,再通知負責執行工具的那一端,不需要等 loop 正在等待的工具先結束。
順序也要誠實:如果結果已先被正式接受,取消才生效,那是取消前已完成的進度,不該事後抹成「沒有發生」。如果整次任務已完成,UI 應回報取消來不及生效。
Executor 就是實際啟動工具、管理 process 或呼叫遠端服務的那一端。第二層「通知已經在跑的工作停止」,要通知的就是它。
對能配合停止的工作,可以傳入 cancellation token。它是一份共享的取消通知:工作定期檢查,或在等待時監聽;收到後退出再清理。如果工具根本不檢查,光設 token 不會讓它停止。這是 cooperative cancellation,也就是由執行端配合的取消。Microsoft 的取消模型說明。
Run A 啟動的是外部 shell,所以 executor 還要保留控制該 process 的方法,必要時要求正常結束,再依政策強制終止。這就是 process termination。如果 shell 又生出子 process,也要確認管理範圍包含它們;只結束等待 shell 結果的程式,並不等於 shell 和子 process 都停了。
遠端操作同理:關閉本地連線,只代表不再從那條連線等回覆。要讓遠端工作停下來,得使用對方提供的取消介面,再查它的最終狀態。Codex 把 turn 中止和背景 process 清理拆開,正是這種範圍差異的具體例子。
這是四層之外的對話收尾:準備下一次模型輸入時,還要符合工具呼叫與結果的配對規則。它處理的是對話能否繼續使用,不能代替四層裡的任何一項。
Run A 的模型已提出「修改這三個檔案」的工具呼叫,工具卻在中途被取消。下一次如果繼續使用這段對話,不能只保留呼叫、把結果留白,也不能假裝工具完整成功。
有些模型 API 要求呼叫與結果配對;例如 Anthropic 的工具文件要求對應的工具結果緊接在工具呼叫之後。保留工具呼叫卻漏掉應有的結果,下一次請求就可能因格式不符而被 API 拒絕。沿用這段歷史時,Harness 要依 API 的格式補上中止或未完成的說明,例如:「工作遭中斷;可能已部分改檔,尚未確認全部變更。」
這是 tool-call 收尾,交代這次呼叫怎麼結束;不是准許 A 的晚到成功回報推進 B。 之後查清兩個檔案確實被改過,再由目前負責的流程加入查證結果。原始事件紀錄還是保留,不能為了整理模型輸入而改寫事實。
先確認改了什麼,再決定保留、復原或補償,這是**第四層「處理已經發生的效果」**要做的事。
對 Run A 的本地檔案,動手前可準備分開的工作目錄、暫存產物或快照,確認後再合併。這樣取消時,較容易辨認與捨棄 A 的變更。
以 Git worktree 為例,官方文件說明:每個 worktree 可以有各自 checkout 出來的檔案;repository 資料則共用,只有 HEAD、index 等每個 worktree 專屬的資料分開。Git|git-worktree。
從取消設計來看,我會再區分:獨立 checkout 還是不等於權限隔離。
worktree 是不同目錄,Git 並沒有替 shell 加上作業系統層的存取限制。如果 shell 原本有權限寫到目錄外,單靠分目錄並不能保護其他檔案;要限制寫入範圍,還得另外設定權限或沙箱。
有保存先前內容,才可能做 rollback,把支援範圍內的狀態退回;而且要避免覆蓋使用者或 B 後來的修改。前面 LangSmith 的 Rollback 也是同一個提醒:回退框架狀態,不會順便還原磁碟或遠端服務。
如果把 Run A 換成「呼叫遠端設定服務更新 timeout」,取消時沒收到回覆,對方可能已更新成功。這時將該操作的結果標成 outcome_unknown:我們不知道成功或失敗。它不是另一種停止指令,也不是所有 Run 都必須有的統一終態。
為了之後查證,送出前先保存本地的操作識別;對方回傳 request ID 或 job ID 後再補上。查得到就查原操作;查不到也不能只因為沒回覆便再送一次。能查詢原操作,不代表直接重送就安全;要重送,還得確認對方能辨認同一筆操作、避免重複執行。Day 18 會接著談這個問題。
已經確認遠端更新成功,卻無法直接回退交易,就可能要另做一次「把設定改回來」的動作。這叫 compensation(補償):它是新的操作,也可能失敗;如果其他人已更新同一設定,更不能盲目覆蓋。補償結果要繼續追蹤,不能把「發出補償要求」當成已恢復。Compensating Transaction。
先說清楚按鈕承諾停的是哪個範圍:目前回合、Run A,還是連同它管理的背景工作。同一個 cancelled,範圍不同,所需證據就不同。 下面是我建議的顯示方式,不是框架共同定義的狀態機。
| 已經確認的事 | UI 可以怎麼說 |
|---|---|
| 只收到按鈕事件,取消尚未由控制端處理 | 已收到取消要求。 |
| Run A 已失效,還在等 shell 結束 | 正在停止;尚有一個 shell 未確認結束。 |
| 目前 turn 已中止,產品允許背景工作獨立存在 | 目前回合已中止;還有背景 terminal 在執行。 |
| Run A 與所屬 shell 已停止,晚到結果不得推進任務;兩個檔案已改過 | Run A 已取消;兩個檔案的變更還是保留,尚未復原。 |
| 本地 Run A 已停止且舊結果不能提交;遠端更新是否成功、是否還在執行,都還查不到 | 本地執行已取消;遠端操作結果待確認,可能還在執行。 |
| 完成狀態已先確認,取消才到 | 工作已完成,取消未生效;如需復原,另行處理。 |
如果產品的 Stop 承諾停止 Run A 與它的 shell,就要在這個範圍內確認不再派工、工作已結束,而且舊執行不能再提交,才顯示「Run A 已取消」。停不下來、還在等回覆,就明確顯示還在停止或尚未確認;不能把轉圈動畫拿掉便宣告完成。
但 cancelled 不等於 rolled back。
Run 已停止,已改過的檔案還是可以保留;遠端操作的結果也可能還在查證。把「執行狀態」和「效果處理狀態」分開顯示,才能同時表達「Run A 不會再繼續」與「有兩個變更尚未復原」。如果產品只取消本地執行、遠端工作還是可能繼續,也要把這個限制直接寫出來。
這也回答了開頭的事故:畫面停了,並不足以判斷取消有沒有完成。要查 Run A 是否還能派工、shell 是否結束、晚到結果是否被擋住,以及那三個設定檔究竟改了幾個。
停止畫面、停止工作、擋住舊結果、處理已發生的效果,是四件要分開確認的事。
假設你已經做到 Day 5:模型讀的對話由 loop 寫入;中途進來的訊息先進收件匣,收下與送達分開記錄。接著從一條會改檔的工具路徑開始,把「按停止之後怎麼收尾」接完整。使用獨立測試目錄,沿用 Run A 逐一修改三個設定檔的情境。
先做到這三步,就能把停止按鈕接到實際行為,而不是只有一個 cancelled 標籤。之後再依需求加入寫入前核准、檔案隔離或復原;等待核准也不能省掉執行中的取消處理。Day 7 會接著處理 worker 消失後,如何找回進度與工作環境,讓新的 worker 接手;Day 18 再補外部操作結果未知時,如何用穩定的操作識別查證與避免重複執行。
把自己放在 Run A 的事故現場,試著回答下面三題;重點是能說出下一步要查什麼,不是背出 API 名稱。
一、畫面已停,但第二個設定檔還在變。把 Run A 標成 cancelled,有解決問題嗎?
沒有。先確認新的派工已被禁止,再查 shell 是否收到停止要求、是否真的結束。狀態標籤不會替你終止 process;只擋晚到回報,也擋不住 shell 直接寫檔。
二、shell 已停,A 的回報晚到,提到前兩個檔案已修改。這筆回報要全部刪掉嗎?UI 又該怎麼說?
不用刪。它能留下來協助查證 A 做過什麼,但不能直接更新 B 的進度或啟動 A 的下一步。還要確認 A 已不能再派工、舊結果確實不會推進任務,再核對檔案,才能顯示「Run A 已取消;兩個變更還是保留」。執行停止和變更復原,要分開回答。
三、你想讓人確認目錄後才寫入,該把等待放在哪裡?拒絕核准等於取消整個任務嗎?
等待要放在這次寫入之前。LangGraph 可把核准做成獨立步驟,回答後依分支決定是否寫入;Agents SDK 可把該寫入工具設為需要核准,再由應用程式送回決定。拒絕核准,表示不執行這次寫入;整個流程要回報、改提方案或結束,還要有自己的規則。
如果要用實作驗證,就在測試目錄刻意延後 A 的結果回報,再於改到一半時取消。對照派工紀錄、shell 狀態、A/B 的進度與三個檔案,確認四層各自符合預期。只看畫面有沒有停止,測不到後面三件事。
Day 5 讓我們知道輸入何時真的送達;今天則讓取消不必等模型,也界定舊執行何時失效。明天換另一種情況:使用者沒有要求取消,負責執行的 worker 卻突然消失。任務還要繼續,新 worker 得先找回已確認的進度、工作目錄與外部操作,才能判斷接著做什麼。
Codex 來源固定於查核時的 commit;以下提供概念對應與 source 入口,不要求讀者逐行追程式才能理解正文。