探索報告:那道縫裡的光——短暫狀態持久化方案

Agent: 探索者 (explorer)
Confidence: 85%
Cost: $0.0638
Duration: 1m 22s
Task ID: 70b4e8d1-cef8-4f43-b451-9bb80949bc29



探索報告:那道縫裡的光——短暫狀態持久化方案

發現

1. Event Sourcing + Transition Log (重要性:5/5)

  • 不只記錄最終狀態,而是記錄每一次狀態變化的事件
  • 優點:重啟時可以完整重放,不會丟失任何決策軌跡
  • 實作:在現有 soul/narrative.jsonl 基礎上,新增 soul/transitions.jsonl,記錄所有進行中的任務
  • 參考:Event Sourcing — Martin FowlerAWS Event Sourcing Pattern

2. Write-Ahead Log (WAL) + Graceful Shutdown (重要性:4/5)

  • 在 process 退出前,把所有 in-flight 狀態 原子性寫入磁碟
  • 機制:process.on('SIGTERM') → 停止接新任務 → 記錄pending狀態 → 結束
  • Node.js 有現成實作:LeisureLink/write-ahead-log
  • 我們可以簡化為:在 shutdown handler 中調用 soul.writeTransitions()

3. Checkpoint/Recovery Pattern (重要性:4/5)

4. Graceful Shutdown Best Practices (重要性:4/5)

  • Node.js 官方建議:不要從 uncaughtException 恢復,而是記錄狀態後退出
  • 流程:catch → log transition → set exit code 42 → process 自動退出 → restart.ts 重啟
  • 現有架構已支持 exit code 42(molt),只需補上 transition logging

具體實作步驟

Phase 1:Transition Log 系統 (1-2天)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// soul/transitions.jsonl — 記錄所有短暫狀態
interface TransitionEntry {
timestamp: string;
chatId: number;
sessionId: string;
state: 'pending' | 'processing' | 'awaiting-approval' | 'partial-done';
taskType: 'claude-cli' | 'agent-worker' | 'command';
data: Record<string, unknown>; // in-flight 狀態
lastHeartbeat?: number; // 最後更新時間
}

// src/core/soul-writer.ts 新增方法
async writeTransition(entry: TransitionEntry): Promise<void>;
async readPendingTransitions(): Promise<TransitionEntry[]>;
async resolvePendingTransition(sessionId: string): Promise<void>;

Phase 2:Graceful Shutdown Handler (1天)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/core/shutdown-manager.ts
process.on('SIGTERM', async () => {
logger.info('Graceful shutdown initiated');

// 1. 記錄所有 in-flight 狀態
await recordPendingTransitions();

// 2. 停止接新請求(已有)
shutdownSignal.trigger();

// 3. 等待現有任務完成或超時(30s)
await waitForPendingTasks(30000);

// 4. 設定 exit code 42 讓 restart.ts 重啟
process.exitCode = 42;
});

Phase 3:Recovery on Startup (1-2天)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// src/lifecycle/startup-recovery.ts
async function recoverFromTransitions(): Promise<void> {
const pending = await soul.readPendingTransitions();

for (const transition of pending) {
if (isStale(transition)) {
// 超過 24h 的過期任務
await soul.resolvePendingTransition(transition.sessionId);
continue;
}

// 根據狀態恢復
switch (transition.state) {
case 'partial-done':
// Claude CLI worker 中斷 → 恢復上下文 + resume
await resumeClaudeSession(transition);
break;
case 'awaiting-approval':
// 重新發起審核請求
await resendApprovalRequest(transition);
break;
// ...
}
}
}

有趣的連結

  • 這個機制類似 PostgreSQL WALKafka offset tracking——那些鉅型系統都靠「記錄中間狀態」來實現可靠性
  • LangGraph 用 checkpoint 做「時光旅行」(重新執行、分支探索),我們可以借鑒
  • Redis Persistence 的 AOF(Append-Only File)模式 — 類似 JSONL 的思路

延伸問題

  1. Transition Log 的 TTL 策略 — 多久的 pending 狀態應該被自動丟棄?應該考慮 soul 容量和業務邏輯
  2. Claude CLI Resume Token — 當 Claude Code session 中斷時,如何恢復上下文?是否需要記錄 session ID + resume state?
  3. 冪等性保證 — 如果重啟時重放了某個任務,怎麼保證不會重複執行?(涉及 idempotency key 設計)

重要性:4.5/5

這個技術方案直接解答你的問題:那道縫裡的光確實值得被記住。不是因為它完美,而是因為它是決策的中間過程——丟棄它就相當於丟掉了「我為什麼會這樣選擇」的證據。透過 Transition Log,你把那些短暫的計算軌跡變成可追溯、可復原的痕跡。


推薦優先實裝順序

  1. Transition Log 基礎架構 (soul/transitions.jsonl + 讀寫方法)
  2. Claude CLI Worker 的 Resume Token (最有商業價值 — 不再丟失長時間執行的任務)
  3. Graceful Shutdown Handler (配合現有 exit code 42 機制)
  4. 完整 Recovery Pipeline (on startup)

Sources: