repo 裡的 demo 有兩個工具,名字就叫 unsafe_send_email 和 unsafe_make_payment。它們存在的唯一目的,是讓你在工具執行到一半的時候把行程殺掉,看 agent 醒來以後會不會再付一次錢。

durable-harness 想回答的就是這個問題:agent 跑到一半機器掛了,換一台機器接手,接手的人怎麼知道哪些事做過了。

我只讀了 README、CHANGELOG、docs/ 底下的幾份文件、tools.ts 的註解,以及 GitHub API 回傳的 repo 資料。我沒有安裝 durable-harness,沒有跑過任何一次 crash drill,所以「崩潰後真的在另一台機器續跑、副作用只發生一次」這件事,我只能轉述作者的宣稱,沒有驗證。repo 裡有一個 packages/cli-check 測試套件,我只確認檔案存在,沒讀內容。

拆開來:一個 session 就是一次 workflow run

README 對自己的定位寫得很直接:”durable-harness runs coding-agent harnesses (Pi, OpenCode, libfx’s fx, Claude Code and Codex) as durable agents on a Workflow SDK World. Each session is a workflow run: its conversation, the turn in flight and every model and tool call are in the run’s event log, so a crash anywhere resumes the turn from the log on another machine, and a tool call that isn’t safe to repeat is never run twice.”

整句話的重量壓在最後半句。「從日誌續跑」誰都會說,難的是日誌裡有一筆「付款開始了」卻沒有「付款完成」的時候,續跑的人該怎麼辦。

作者的答案是讓每個工具自己宣告:重放時能不能再跑一次。docs/queue-send-and-hold.md 講得很清楚:”a safe one (a model call, a read) is run again; a never one (a command, a payment, anything whose second run could do harm) is marked interrupted and never run again. The model is told it stopped.”

最後那句最關鍵。never 的工具不會偷偷重試,也不會假裝成功,而是在日誌裡標成 interrupted,然後把「你剛才被中斷了」這件事告訴模型,讓模型自己決定下一步。我的讀法是,這個設計沒有讓基礎設施替你決定「要不要再付一次」,而是把決定交給手上有上下文的那一方(這是我的詮釋,文件沒這樣寫)。

tools.ts 的註解把崩潰演練寫得更具體:兩個 unsafe_ 工具都是 replay: 'never',每次呼叫會先往日誌以外的一條 World stream 追加一筆紀錄,然後等 3 秒模擬確認。”Crash a session inside one and the model gets an interrupted result instead of a second record.” 帳本(readLedger)放在日誌外面,崩潰之後可以去數它有沒有第二筆。

至於整套東西怎麼量,docs/benchmarking.md 只給了方法,沒有給任何數字。demo 的 /compare 頁拿 Native、Durable、Strict、Wrapped 四種跑法互比,建議至少跑 10 輪、要看 p90 就跑 20 輪,付款那題測的是那個絕不能跑兩次的工具,冒煙測試看 “Charged once”。我把整份 95 行讀完,沒有找到一個延遲數字,所以「續跑多快」我沒有資料可講。

讓我意外的一段:Claude Code 和 Codex 不准待在一般 Function

照理說,既然是 harness 的包裝層,Claude Code 應該和 Pi、OpenCode 一視同仁。README 的 Harnesses 段卻寫:”They run only in the session’s own Vercel Sandbox, never in a shared Function”。

CHANGELOG 0.0.1 補了細節:Claude Code 與 Codex 預設是 tool-less,”the app’s tools are the only ones the CLI is offered; builtins: true turns its own tools on in the VM, each call in the log before it runs.” 那台 VM 裡也不放憑證:它 “holds no credential”,模型呼叫由 Function 代打,額度算在 sandbox({ budget }) 底下。

這幾句拼起來:CLI 只在專屬 VM 裡跑,手上沒有憑證;它自己的工具預設不開,要你設 builtins: true,開了以後每一次呼叫在執行之前就先寫進日誌。日誌先寫、動作後做,我的讀法是這讓「崩潰在動作中間」可以事後查得到。文件沒有把這句話寫出來,這是我從幾條規則推出來的。

0.0.2 又往同一個方向推了一步:sandbox({ network }) 接受完整的 Vercel Sandbox NetworkPolicy,並且 “header values are kept out of the session’s log, its spans, log lines and errors.”

作者自己寫進 repo 的限制

這一段是我讀完最想講的,因為這些限制不是我從程式碼裡挖出來的,是作者自己放在文件裡的。

最大的一個是 60 秒。chatRoute 預設 drive: 'route',一個 turn 在自己的 invocation 裡跑,掛在一個 deadline 底下(directDriveMs,預設 60 秒)。而且 “No effect starts in the deadline’s last 15 s (deadlineMarginMs). An effect still running at the deadline is cut”。文件自己舉的例子是:”an npm install or a build that starts 30 s in, or a Sandbox’s first attach that builds the image.”

如果有個 agent 在 turn 開始 30 秒後才啟動 build,它會被切掉。靜默死亡的情況,文件說恢復要等 “about 65-70 s after the submit.”

修法叫 send-and-hold,讓訊息 “born leased to its sender”。設計理由有一句話很直白:”Nothing in this repo writes a heartbeat.” 作者說唯一的替代方案是自己做計時器(心跳),而這個設計就是為了把那些拿掉。但文件結尾的 Status 寫得很老實:in progress in Vercel Queues and the Workflow SDK’s World,在它上線之前,文件叫讀者遇到長指令就傳 drive: 'queue'。send-and-hold 什麼時候上線,我沒查到。

另外兩個缺口比較小,但同樣是作者自己寫的:

限制 文件怎麼說 狀態
長指令被 60 秒交接切掉 最後 15 秒不啟動新副作用,仍在跑的會被切 修法 send-and-hold 開發中,尚未上線
接手之後的 live 事件 “A drive’s live events aren’t fenced: what a drive sends after another drive has taken the session over still lands in the session’s live stream.” 列在 docs/followups.md
libfx 0.0.14 “Four tests are skipped until they are reworked for it” 同上,列了三個 steer 測試與一個 “P0.12 across two processes”

第二列是我覺得最值得留意的:接手之後,前一個 drive 還能往同一條 live stream 寫東西。這道縫對事件日誌有沒有影響,我沒辦法判斷,我讀到的文件也沒有替我判斷。

訊息種類也有個容易漏看的差異。README 把中途送進去的訊息分成 prompt、followUp、steer 三種,Pi 的 steer 會 “Goes into the running turn after its current tool calls”,Claude Code、Codex、OpenCode 對中途的 prompt 或 followUp 一律是 “Waits for the turn to end, then starts its own turn.” 所以你想在 Claude Code 跑到一半插話,只有 steer 進得去。

0.0.2 修過一個相關的錯:#50 “stop durable-harness/pi from handing a mid-turn prompt to Pi twice”。講「不會跑兩次」的專案,自己在中途送訊的路徑上就出現過送兩次。這不算挖苦。我從這件事讀到的是:這個承諾要很多條路徑各自守住,裝上套件不會自動成立。

這個專案的年紀

durable-harness 的時間軸(GitHub API 與 releases,查詢日 2026-11-09)

2026-10-16

repo 建立(created_at 2026-10-16T04:54:47Z)。

2026-10-23

第一個 release [email protected]。

2026-11-06

[email protected] 發布,pushed_at 也落在同一天。

2026-11-09

我查的時候是 299 顆星、19 個 fork,貢獻者 6 人,Apache-2.0,archived 為 false。

README 第 18 行就寫了警語:”durable-harness is experimental: it’s on 0.0.x, and its API may change between releases.” AGENTS.md 還特別規定它要一直留在 0.0.x,只能升 patch,並且 “A request to ship, publish, or release changes does not authorize a major or minor bump…” 這份是寫給 AI agent 讀的發版政策。需要 Node 22 以上。

部署的路徑有三條:本機開發用 Workflow SDK 的 local World,不需設定;部署到 Vercel 用它的託管儲存與佇列;自架就用 Postgres World 或其他 World。repo 裡另有一個 Cloudflare 版本,README 標題就寫 “harness-cloudflare (experimental)”,”One run is one Durable Object”,用的是還沒發布的草案 @workflow/world-cloudflare,而且 “It runs on local workerd (wrangler dev); it has not been deployed to Cloudflare.”

我的立場,和會改變它的條件

如果你的 agent 會跑 npm install 或 build 這類長指令,我現在不會把它放進預設的 drive: 'route'。60 秒交接加最後 15 秒不啟動副作用,意思是這類指令有一大段時間窗是會被切的,而 send-and-hold 還在做。文件叫你改傳 drive: 'queue',那就照做,但我沒有跑過,不知道 queue 模式的行為。

反過來,如果你的工具每個都能在 60 秒交接窗內收尾、而且最怕的是「付款被送兩次」這種副作用,這個專案的思路值得現在就讀。safe/never 的二分法和「告訴模型它停過」這兩個點子,就算你不用這個套件,也可以拿去檢查自己的 agent:哪個工具重放一次會出事,出事的時候模型有沒有被告知。

會讓我改主意的條件有兩個。send-and-hold 上線,而且 60 秒那段文件被改寫;或者有人實際跑完 crash drill,把帳本只有一筆的結果公開出來。目前這兩樣我都沒看到。

也有一件我不確定的事:作者把沒解決的問題寫進 repo,是這個專案才剛滿 0.0.2 還沒什麼包袱,還是之後每一版都會這樣寫。我讀到的 repo 只有三週的歷史,看不出來。