一個定位「極簡無記憶」的 Telegram AI Agent,能否在不帶對話歷史的架構下完成需要跨輪確認的多階段任務?
背景:一個架構性矛盾
LazyHole-Agent 的核心設計哲學是「無記憶、無狀態、隨插即用」。每次收到 Telegram 訊息,handle() 只組兩則訊息給 LLM:
const messages = [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: text }, // 就這一句
];
這讓系統極簡,但也製造了一個矛盾:多階段 Skill 天生需要跨輪上下文。
以 blog 發布為例,流程是「概念確認 → 細節審閱 → 發布」。Turn 1 確認了標題,Turn 2 傳來一句「好」——LLM 根本不知道「好」是在回應什麼。
概念分層:Draft ≠ History ≠ Memory
| 類型 | 生命週期 | 誰來管 |
|---|---|---|
| Draft(草稿) | 單次任務內,完成即清 | Agent 主動存取 |
| History(對話歷史) | 永久 | Telegram 本身 |
| Memory(長期記憶) | 跨對話 | 本專案不支援 |
黃金法則:若刪掉這個欄位,下一階段能從當前訊息重建嗎?能 → 不寫。不能 → 寫。
三次設計演進
第一版:Skill 直接存檔
src/utils/drafts.js 提供 loadDraft / saveDraft / clearDraft,Skill 直接 require。缺點:第三方 Skill 必須修改,違反零侵入目標。
第二版:Draft 升級為 Agent Tool
把 drafts 包成三個正式 agent tool:save_draft / load_draft / clear_draft,並在 BASE_SYSTEM_PROMPT 加規則「先 save_draft 再回覆用戶」。理論上第三方 Skill 零改動。
第三版:Pending Draft 注入 System Prompt
handle() 起始掃描 ~/.lazyhole/drafts/,有未過期草稿就注入 system prompt 尾段,讓下一輪 LLM 能把「好」解讀為「延續任務」。
A/B 測試結果(MiniMax-M2.7)
建立 blog-poster-minimal——只描述多階段意圖,完全不提 save_draft。
Turn 1:
- ✅ 讀取 Skill、產出 title + summary
- ❌ 完全沒呼叫 save_draft(System Prompt 規則輸給 Skill body 的具體流程描述)
Turn 2(傳「好」):
- ✅ 嘗試了 load_draft(方向正確!)
- ❌ namespace 猜成 global,撈不到 → 回覆預設招呼語
核心發現
- Resume 機制邏輯是對的:LLM 確實願意在模糊訊息下去查草稿,問題在於沒草稿可找。
- Skill body 比 System Prompt 更能左右行為:MiniMax 更信 Skill 的具體描述,不信 System Prompt 的抽象規則。
- 模型能力是架構的隱形變數:「零改動第三方 Skill」對強模型成立,對中階模型不成立。
取捨框架
| 方向 | 作法 | 適用情境 |
|---|---|---|
| A. 妥協 | Skill body 明寫 save_draft 指令 | 自己維護的 Skill、穩定優先 |
| B. Agent 反射 | 有輸出但無 save_draft 時自動補存 | 複雜,易誤判,不推薦 |
| C. 換強模型 | Claude / GPT-4 遵從性更高 | 有預算、Skill 生態要開放 |
目前選 A。
後記
這次最有價值的不是成功,而是「失敗的方式」——Turn 2 幾乎做對了所有事,只差一個 namespace。架構設計是對的,差的是 Turn 1 的存檔觸發。