第三部|可靠執行 · 第 7 章
Cancellation 與 Durable Compaction
處理 abort race、合法 transcript,以及 active-context cut 與 durable source partition 的差異。
一句話掌握本章核心:取消(Cancellation)不是單純「把 Promise 拋棄」,而是必須遵循 先記 Abort 意圖再發送中斷信號 的順序。歷史壓縮(Compaction)則透過本文所稱的 雙切點(Two-Cut) 交易縮減 Active Context,同時保留原始 message facts 供復原與一致性稽核。
中斷的正確順序:先持久化意圖,再發送信號
設想情境:工程師在 bash deploy.sh 執行時按下了 Esc。如果系統直接呼叫 abortController.abort() 卻未先寫入磁碟,一旦行程在此刻崩潰,重啟後系統完全不知道使用者曾要求中斷,可能會誤判為未知錯誤而發起重試。
# 概念偽碼。TypeScript 實際介面名稱是 session.append(...)
1. 持久化中斷意圖 ──→ append(abortRequested) 完成
↓
2. 發送中斷信號 ──→ 向當前 Model API / Bash Child Process 傳遞 AbortSignal
↓
3. 補齊結構化狀態 ──→ 將未完成的 Tool 標記為 interrupted,保持 Transcript 合法
↓
4. 寫入終局記錄 ──→ append(operationFinished: aborted)
↓
5. 釋放本次資源 ──→ 關閉 Session/連線。已完成 bg start 的行程需另行 bg stop 或由部署 supervisor 清理
Cancellation 依 Phase 精準分流
| 當前運行階段 (Phase) | 具體中斷動作 (Signal Action) | Durable WAL 狀態修復 (Reconciliation) |
|---|---|---|
| Model Phase | 透過 AbortSignal 取消進行中的 Provider HTTP request | 將未結算的 Attempt 標記為 stepFailed(aborted) |
| Tool Phase | 將 AbortSignal 傳給當前 Tool。foreground bash 交由 Node child-process API 中斷。本路徑沒有通用的 TERM→grace→KILL process-tree supervisor。bg stop 是另一條具兩階段 signal 的管理路徑。 | 已啟動的 Tool 補寫 interrupted。同一 assistant response 中尚未啟動的 calls 補寫 aborted |
| Compaction Phase | 中斷摘要生成請求 | 關閉當前 Compaction Attempt,結束該 Operation |
Abort Race 競爭條件的裁決鐵律
當使用者中斷與模型/工具回傳互相競爭時,關鍵不是牆鐘上的「同一毫秒」,而是 durable append 與 serialized settlement 的先後:
| 競爭勝出方 | 判決條件 | 最終歷史呈現 |
|---|---|---|
| 成功結算先完成,active 已解除 | 結果已 append,且該 active operation 已由 settlement 清除 | 後到的 Abort 找不到 active operation,不改寫既有結果。Runtime 可依既有狀態繼續。 |
abortRequested 先落盤 | 中斷意圖已成功寫入 WAL | 遲到的成功回應一律作廢丟棄,該操作嚴格記錄為 aborted。 |
Compaction 歷史壓縮:雙切點(Two-Cut)架構
重要觀念:目前的 Compaction 不會從 Session JSONL 中刪除舊 message facts。它建立新的摘要節點(Checkpoint)來縮減 Active Context。這是現行格式行為,不代表跨備份、retention policy 或儲存媒體的「永存」承諾。
- active context(壓縮前) e1e2…eN-6eN-5…eN
- 找切點 保留最近 6 則 message,再往前推到 user boundary。找不到就放棄本次 compact
- model 產生 summary 切點之前的 entries(+ prior summary)→ 一則摘要文字
-
寫入 durable facts
compactionStarted記錄 partition/digest。compactionentry 保存 summary + retainedTail(原始 facts 仍在 append-only log) - active context(壓縮後) summaryeN-5…eN → 下一次 model request 只看這些
/compact 的資料流。淡出的標籤僅移出 Active Context。在目前未執行 retention/deletion 的 Session 檔中,原始 facts 仍保留。
雙切點(Two-Cut)的嚴格定義
Schema 上刻意把「開始時鎖定的來源 partition」和「成功後產生的 checkpoint」分開:compactionStarted 是 durable intent 與 source digest。compaction entry 是 summary 結果與可直接重建 Active Context 的 retained tail。這讓每個欄位都有明確用途,也避免把審計欄位和模型摘要結果混在同一個節點。
| 切點維度 | 主要服務對象 | 運作機制與邊界保證 |
|---|---|---|
| Track 1: Active-Context Cut | LLM 大腦(推論視窗) | 保留最近至少 6 則訊息,並自動向前尋找 user 邊界(避免拆散單次完整問答)。前次產生的舊 Summary 會參與本次新摘要的計算。 |
| Track 2: Durable Source Partition | 一致性稽核與 Crash Recovery | compactionStarted 記錄 compactedEntryIds、retainedEntryIds 與 sourceDigest。compaction entry 則記錄 summary、compactedThroughEntryId 與 materialized retainedTail。Reducer 可偵測內容、順序或 partition 未同步更新。但未加密 SHA-256 不是 MAC/signature,能寫檔的攻擊者仍可重算,因此不構成 tamper-proof audit log。 |
動手驗證 (Hands-on Lab)
執行決定性的中斷與壓縮回歸測試:
# 1. 執行 TypeScript 的中斷與壓縮整合測試
cd typescript
node --import tsx --test --test-name-pattern="abort|compaction" tests/index.test.ts
# 2. 檢視官方持久化 Fixtures 中的 abort 與 compaction 事實
cd ..
grep -R 'abortRequested\|compactionStarted\|sourceDigest' schemas/session/fixtures | head -20
至此,我們已經構建出一個能在已定義的錯誤、崩潰與中斷情境下復原,並透過有損摘要控制 Active Context 成長的 Agent 核心。但我們該如何建立自動化測試體系?如何監控生產環境指標?又該如何劃定 MCP 與多租戶的最高安全防線?最後一章我們將全面走向 Production 實戰。