AI News HubLIVE
站內改寫2 分鐘閱讀

構建能夠在自身執行中存活的智能體

Conol 團隊分享了一次因 RDS 重啓導致 agent 停擺 9 小時的故障,並解釋如何借鑑代數效應思想,把 agent 的每一次執行視為可持久化的數據,而不是依賴進程調用棧。文章介紹三種模式——動作即效果、純函數式狀態變更、狀態與後續效果原子提交——以及基於 Postgres 的隊列、租約和至少一次投遞機制。

來源Hacker News AI作者: greatony

某天晚上,Conol 的 agent 大約有九個小時沒有動彈。沒有崩潰,隊列裏的工作就那麼停滯。起因很普通:RDS 重啓期間,一個瞬時數據庫故障出現在 worker 的輪詢循環中。循環不再消費隊列,但進程因為仍持有打開的資源而繼續存活,沒有任何 supervisor 替換它。進程像活了一樣,卻什麼也不做。拯救他們的不是循環本身,而是循環下面的基石:agent 的全部狀態和已產生的 effects 都持久化在 Postgres 中。重新啓動 worker 後,隊列從停下的位置繼續;他們不需要從日誌重建用户會話或重放對話。這件事引出一個觀點:讓系統可靠的關鍵,不是更好的 prompt,而是把 agent 的“一輪執行”當作持久化數據,而不是一段調用棧。

很多 agent runtime 一開始是這樣:一個 async 函數持續調用 LLM、執行 tool call,把所有 messages 存在本地變量和調用棧上。這在筆記本上沒問題,在生產環境卻致命:進程一旦掛掉或卡死,整輪 turn 就丟失。模型再聰明,運行時也取決於持有棧的那個進程有多可靠。

最有幫助的心智模型來自代數效應。它區分“要做什麼”和“怎麼做”:operation 描述請求,handler 決定含義,函數執行 perform 時不知道如何處理。Effect handler 可以看作可恢復的異常;例如 greet() 執行 AskUser 操作,handler 用 continuation k 返回 "Ada",計算從 AskUser 處恢復。OCaml 5 的 one-shot continuation、monads 等都有對應位置。Moggi 1991 引入 monads;Plotkin 和 Power 提出 effects 的代數描述;2009 年 Plotkin 和 Pretnar 形式化 effect handlers。OCaml 5 的 Eio、研究語言 Koka、Eff、Effekt 都探索了這些思想。React Suspense 和 durable workflow engine 也有類似形狀,但不等同。

Conol 沒有實現 continuation 或 effect types,只取了分離思想,用持久化數據實現。effect 變成寫入 Postgres 的帶標籤值,比如 { type: "llm", sessionId };handler 變成讀取並解釋 effect 的 worker;resumption 是重建持久狀態後繼續,而不是恢復掛起的棧。隊列可被檢查,測試也能用確定性 handler 替換真實實現。兩個字段承載調度:visibleAt 決定 effect 何時可被認領;穩定 id 讓重複的定時任務替換已有隊列行而不是重複插入。

狀態推進通過純 mutation。mutation 接收 sessionId 和 from state,返回新 state 以及這次轉移產生的 effects;drop 表示輸入過期,replay 表示把 effect 放回隊列。每次提交都帶單調 version,寫入用 compare-and-swap。如果版本不匹配,runtime 會拿最新 state 重跑 mutation。純函數讓 rebase 安全:mutation 可能被多次求值,但只有贏家提交,且外部 I/O 不能放進 mutation,否則 CAS 重試會重複執行。後續工作以 ops 返回,稍後作為 effects 運行。

狀態和後果必須一起移動。Conol 在同一個 Postgres 事務中先 CAS 更新 agent_sessions,再插入 effect_queue,從而保證狀態與後續 effects 同時持久化;否則可能狀態推進但 effects 丟失,或 effects 提交而 agent 不知道。Worker 從隊列領取 effect 時使用 lease:claim_token 和推遲 visible_at;確認、重試、續租都校驗 token。這提供至少一次投遞,因此結果 mutation 必須冪等地處理重投遞:tool result 以 toolCallId 寫一次,LLM 流事件綁定 llmCall.id,重複 effect id 替換隊列行。文章也特別提醒,重放安全不保證外部工具恰好執行一次;調用支付 API 等副作用需要自己的冪等鍵。

一個帶工具調用的 turn 會變成五次持久化提交:用户 effect 到達後提交消息併發出 llm effect;llm handler 運行模型後提交 assistant 消息併發出 tool_call effect;tool_call handler 運行工具、提交一次結果併發出後續 llm effect;第二次 llm handler 用工具結果提交最終回答;沒有剩餘效果時結束會話。每兩步之間 worker 全消失也沒關係,新 worker 可以從最後提交的狀態繼續。於是崩潰變成重放:worker 認領 effect、開始處理,之後無論發生什麼,只要狀態提交,系統就能前進;如果還沒提交,則 effect 會再次被投遞。這就是 Conol 把智能體的執行變成可存活數據的方式。