跳到主要內容

【2026 最新】Terse Durable Actors 是什麼?雙客戶端、Interleave 與中斷驗收教學

最後更新: ·
Terse Durable Actors:雙客戶端、Interleave 與狀態保存/續跑驗收

你和同事同時打開一個 AI 聊天室:兩邊能看到同一段對話,伺服器重開後訊息也還在。這就代表剛才生成到一半的回答會接著寫嗎?Terse Durable Actors 最值得新手弄懂的,正是「資料留下來」和「工作接著跑」之間的界線。

這篇寫給第一次接觸 Actor、願意複製幾行指令的讀者。先用聊天室理解原理,再建立兩個客戶端的驗收流程:共享狀態、等待時的插隊、重啟和重複請求。內容依截至 2026 年 10 月 8 日的官方文件、0.7.16 套件與固定版本原始碼整理;本次沒有執行本機 Actor 服務,下面清楚區分官方測試斷言與建議練習,不把它們寫成 AlphaLab 實測。

先說結論:Terse Durable Actors 保存狀態,續跑要另驗

Actor=有地址的共享筆記本+安排呼叫的管理員;筆記保存,不等於工作自動續跑。兩個人找同一個地址,就找同一本筆記;管理員決定誰先改、等待時是否讓別人進來。這比把 Actor 想成「另一款聊天模型」更準確。

  • 先看地址:同一專案、Actor 類別與 Actor ID 才是你要核對的共享對象。
  • 再看提交:@Persisted 欄位的保存有成功呼叫的邊界,不能把畫面上的每個字都當成已提交。
  • 最後看中斷:留下 waiting 或 pending,表示保存了狀態;是否重做、接續或人工處理,是另一個驗收項目。
Terse Durable Actors 的狀態保存與工作接續驗收示意
兩項驗收各自對帳:讀回狀態,與接續完成工作。

Terse Durable Actors 是什麼?把五個零件拆開

Actor(行為者模型)是一種把狀態和操作聚在同一個單位的設計。Terse 的官方專案用它支援多人聊天室、協作文件和 Agent 系統:你的模型負責生成內容,Actor 負責保管共享資料與協調呼叫。若還分不清模型與執行層,可先讀Agent Harness 是什麼。

① Actor ID:「這一本筆記的地址」

類別像筆記本的款式,ID 像編號。ChatRoom.get("lobby") 指向一個房間;換成 "other-room" 就是另一個房間。驗共享狀態時,先核對專案設定、類別名稱與 ID,避免兩邊各寫各的,卻誤以為同步壞了。共享地址也不是使用者權限:誰能進房間,仍要由應用程式檢查。

② Persisted:「正式存進去的內容」

@Persisted 是標在欄位上的裝飾器,也就是告訴框架如何處理這個欄位的標記。官方TypeScript 指南說明,成功的方法呼叫會保存欄位;Actor 的 this.db SQL 寫入也與這些欄位一起提交。白話說,是把這次工作辦完後蓋章,不是每改一個字就蓋一次。

③ 一般方法:「輪到你,先把這次做完」

一般呼叫會持有 Actor,包含遇到 await 的等待時間。Await 是「先等網路或其他工作回來,再繼續」。即使 CPU 暫時沒在計算,下一個新呼叫仍可能要排隊。這能簡化共享資料的修改,代價是長時間生成會讓其他操作等得更久。

④ Interleave:「等外面時,讓下一位進來」

在公開的非同步方法前加 @Interleave,可讓其他呼叫在它等待時執行。官方裝飾器定義同時提醒:等待期間狀態可能變,而且整個 Actor 類別的錯誤回滾會被停用。回滾就是失敗後把資料退回原樣;這不是只影響被標記的方法,也不是按下「加速」而其他保證原封不動。

⑤ WebSocket:「一直開著的消息通道」

WebSocket 讓服務和客戶端保持連線,方便把新訊息推到多個畫面;RPC 則像直接打電話請遠端方法辦一件事。官方聊天室用 onConnect 傳既有 history,收到訊息後保存並 broadcast 給連線中的使用者。廣播是畫面交付,提交是保存結果,兩者要分開核對。

第一步:先用官方無模型聊天室驗共享與重啟

痛點是:還沒理解狀態,就先把模型費用、串流與網路錯誤混在一起。解法是先用沒有模型呼叫的 chat 範本。官方Chatroom Quickstart要求 Node.js 22.19 以上與 Bun 1.3.9 以上;已有相容環境才繼續。先用 node --version 與 bun --version 核對,不把缺少執行環境當成程式錯誤。

  1. 在獨立資料夾依序執行 npx durable-actors@0.7.16 init chat-example --template chat、cd chat-example、npm install、cp .env.example .env。
  2. 先跑 npm run dev:actors,等 Ready;另一個終端在同一資料夾跑 npm run dev。
  3. 開兩個 http://127.0.0.1:3000 分頁,分別送「A:測試一」和「B:測試二」。核對兩邊 history,而非只看輸入框旁的成功提示。
  4. 兩則訊息完成後,在 Actor 終端按 Ctrl+C,再跑 npm run dev:actors;重新整理兩個分頁、重新連線,核對文字和順序。

這一輪驗的是已完成訊息的保存,不是突然斷電,也不是生成中斷。固定 CLI 版本後,仍保存產生的 package.json、lockfile 與 npm ls durable-actors 輸出,確認專案實際載入哪個版本。每輪記錄房間 ID、送出時間、收到內容與重啟結果;不要預填「一定成功」。官方範本以訪客加入,之後接真實用戶前要加登入與房間存取檢查。

第二步:兩個客戶端比較一般方法與 Interleave

痛點是:兩個畫面都顯示訊息,仍看不出等待時能不能讀狀態。解法是把模型換成固定等待的假回應。在 src/actors.ts 的既有 ChatRoom 類別內加入以下兩個方法,保留原本 history 與 WebSocket hooks:

async load() { return this.history }

async slow(label: string) { this.history.push({ name: "probe", text: label + ":start" }); await new Promise(resolve => setTimeout(resolve, 3000)); this.history.push({ name: "probe", text: label + ":end" }); return this.history }

三秒是這份練習選的等待設定,不是效能數字。這段以 RPC 回傳 history,沒有新增即時廣播;請看客戶端輸出,別把聊天畫面沒刷新當成方法沒執行。它也省略取消與 timeout 處理,目的僅是觀察兩個呼叫的先後,不適合直接拿去接模型串流。

依CLI 文件,方法改好後執行 npx durable-actors@0.7.16 generate。把 client.ts 保存成:import { actors } from "./generated/index.js"; const room = actors.ChatRoom.get(process.argv[2] ?? "serial-probe"); console.log("before", Date.now()); const result = process.argv[3] === "slow" ? await room.slow("A") : await room.load(); console.log("after", Date.now(), JSON.stringify(result));

終端 A 跑 bun client.ts serial-probe slow;三秒等待期間,終端 B 跑 bun client.ts serial-probe load。依文件的一般方法語意,B 應等 A 結束。這是待驗預期,不是本次跑出的時間紀錄;保存兩邊 before/after 與回傳陣列,核對 B 何時真正返回。

第二輪在檔案 import 加入 Interleave,並在 slow 前加 @Interleave。等服務完成 reload,再 generate;用新的 interleave-probe ID 重跑兩個命令,避免混入上一輪 history。觀察 B 能否在 A 的 end 之前讀到 start;若手動操作太慢,這一輪無法驗等待重疊,重新做一次並記錄時間。

可插隊不等於所有呼叫任意並行。官方執行層測試分別檢查一般方法仍彼此序列化、一般方法會阻擋新到的 Interleave 呼叫,以及已開始的 Interleave continuation 可在另一個等待期間恢復。Continuation 就是等完後剩下的程式。讀共享狀態時,不能假設 await 前看到的值,回來後仍相同。

想把事件順序看得更清楚,可依 CLI 文件在服務運行時執行 npx durable-actors@0.7.16 observe 開啟觀測介面;保存自己的呼叫與等待紀錄。下圖是官方公開的 Requests 範例,讓你認識方法、WebSocket 與等待時間的視圖,裡面的 readFile/writeFile 是官方示例,與本文聊天室測項不同。

Terse Durable Actors 官方 Requests 觀測介面範例
Step 2.1 — 觀測自己的呼叫先後;圖為 Terse 官方 README 的 Requests 介面範例,並非本文的執行紀錄。圖片來源:Terse 官方專案。

第三步:中斷演練要驗「存了什麼」,再驗「誰會接手」

痛點是:畫面看到 start,就認定它已永久保存。解法是記錄提交邊界。Interleave 中,另一個成功完成的呼叫可能保存當時共享狀態;因此 load 不只是旁觀,也可能改變中斷前的提交情況。安排故障時,必須記錄是否有其他呼叫完成,不要把兩輪不同的操作序列當成相同實驗。

官方重疊與重啟測試提供一個很清楚的檢查規格:先讓 hold 等待、完成 read,再讓 Actor 程序退出;測試預期中斷呼叫回報 outcome_unknown,恢復後仍讀到 count: 11, waiting: 1,完整停啟後再核對同一狀態。這是官方測試程式的斷言,我們本次沒有執行它,也不把這個數字當成 production 成功率。

值得注意的是,waiting 留下來,並不表示原本等著的工作完成。作者在Show HN 的直接答覆明確區分 durable state 與 durable execution:這個專案管理的是前者。把 Actor 重啟比作重新拿到筆記本;恢復到哪一步、是否要重做模型請求,得有自己的作業規則。

如果你要在自己的隔離範本加故障注入,可參照官方測試 Actor的 async crash() { process.exit(1) },在等待且已有另一個成功讀取後,透過同一 Actor 呼叫它。先把客戶端錯誤完整保存,再重啟並 load;只在你專用的本機假資料服務做這一輪,不要在共用服務或公開 demo 注入退出。它刻意終止 Actor 程序,與 Ctrl+C 正常停啟是不同測項。

第四步:requestId 與去重,別用錯一張單據

痛點是:請求失聯後重送,可能多寫一則訊息或多呼叫一次模型。解法是分開傳輸追蹤 ID 與業務作業 ID。官方RPC 協定明說 requestId 識別一次嘗試,不會去重執行;outcome_unknown 表示可能已執行。它不是「失敗所以可以放心再做」。SDK 對明確未執行的特定路由錯誤有有限重試,不能套到結果不明的呼叫。

實作上,另設應用程式的 jobId:同一個「新增訊息並產生回答」意圖,重送時沿用它。可先設計 pending → completed 或 failed 狀態,保存輸入指紋、結果與錯誤。這是你要加入的應用邏輯,不是 Terse 自帶的自動續跑開關。正式實作的資料結構與提交方式,接著讀最小 Agent Harness 實作會更容易對上。

  • 同 ID、同內容:已完成就回既有結果;pending 要回明確的等待/待處理狀態,不重開一輪。
  • 同 ID、不同內容:拒絕衝突,不能悄悄沿用舊結果。
  • 跨 await 的第二次呼叫:確認它看到 pending 後不會再啟動相同工作,並驗 Interleave 類別的失敗狀態。
  • 模型已回覆、結果尚未保存時中斷:另記外部請求結果與重做策略。Actor 資料提交和遠端模型副作用不是同一筆本地交易。

這份去重規格還需要保留期限、容量上限與重啟後處理 pending 的規則。先寫出四個測例和預期結果,再寫程式;想把它變成持續驗收,可接Agent 回歸測試。

什麼時候用 Actor?什麼時候還要工作流?

若你的核心問題是「多人共享同一個房間狀態、低延遲讀寫與推送」,Actor 是值得測的構件。若問題是「一個多步作業失敗後如何重試、保留步驟進度並繼續」,要另外評估耐久工作流。Temporal 的 Workflow Execution 文件描述的是這一類執行生命週期;這是選型問題的區分,不是宣稱兩者只能二選一。

也別把同一 Actor 的協調,放大成所有資料都已解決:跨房間查詢、兩個 Actor 一起完成一件事、外部 API 副作用,都要有另外的設計。模型快慢與 Actor 等待快慢也各有來源。先用假回應驗排隊、提交與去重,再接付費模型,才知道異常落在哪一層。這種驗收方法可參考AI Evals 入門。

常見問題:八個新手判斷

Terse Durable Actors 是一款 LLM 嗎?

它是應用程式構件。模型生成答案,Actor 保存與協調狀態。第一輪用無模型的聊天室,就能學這個機制。

兩個分頁就是兩個 Actor 嗎?

不一定。客戶端數量與 Actor 地址是不同層。先查同一專案、類別和 ID 是否一致。

看到 start,就確定磁碟已保存嗎?

要查提交時點。畫面或日誌顯示,只證明你收到那個觀察;再看哪個呼叫完成、重啟讀到什麼。

加 Interleave 只改排隊嗎?

也改失敗處理。0.7.16 的文件與原始碼明說整個類別停用錯誤回滾,應把失敗測例一起補上。

重啟後 waiting 還在,算回答續跑成功嗎?

還要驗工作。waiting 是保存的狀態。要另查誰重新接手、是否得到完整回答及是否重複呼叫。

沿用 requestId 就能去重嗎?

官方協定明說不能用它去重。另設業務 jobId、輸入指紋和結果紀錄,再驗重送與衝突。

本機聊天室成功,就適合 production 嗎?

還要擴大驗收。登入、房間授權、資料備份、恢復、部署故障與負载是另一組具體測項;兩個分頁的成功範圍很小。

第一天應該接真實模型嗎?

先用假回應。等地址、事件順序、重啟結果和重送行為都能對帳,再把模型延遲與費用加入。

給新手的三個重點

  • 共享先核對地址,保存先核對成功提交。
  • Interleave 讓等待期間有其他進展,同時要求你重新設計失敗與狀態檢查。
  • 把正常停啟、突然中斷、結果不明與重送拆成四個測項;一項通過不替其他項蓋章。

下一步:交出一份兩個客戶端的事件紀錄

記住那本共享筆記:筆記保存,不等於工作自動續跑。今天先用官方 chat 範本留下兩個分頁的訊息與正常停啟紀錄;再做一般方法/Interleave 的等待對照,把看到的順序、版本、Actor ID 和提交操作記下來。接著才加入故障與 jobId 去重。想繼續建立完整系統,可從AI 文章總覽選下一個主題,或到AlphaLab 課程延伸你的實作路線。

ALPHALAB 社群

有問題?來 Telegram 聊

和 Terry、編輯、其他網友一起討論這篇文章。提問、分享觀點,回覆更即時。

加入 Telegram 討論

📩 訂閱 AlphaLab 電子報

每週最多三封:一封 Weekly 週報與最多兩封關鍵 Alpha Signal。

我們不會 spam,隨時可退訂。已訂閱?管理主題偏好(會寄登入連結到你的信箱)