前言:AI Agent 的維護成本問題

大家都在聊怎麼讓 AI agent 更聰明,很少人聊怎麼讓 agent 更省。

真實數字:我的 OpenClaw agent 一開始全用 LLM heartbeat,每小時燒 token 檢查「有沒有事」。一天 24 次 LLM call,90% 的回覆都是 HEARTBEAT_OK——什麼事都沒發生。

問題不是 LLM 太貴,是用 LLM 做不需要 LLM 的事。

這篇記錄了一個 AI agent 從「LLM 做所有事」進化到「只做該做的事」的過程——heartbeat 系統三次重構,self-improvement 系統上線,以及一個反直覺的結論:AI agent 成熟的標誌不是用了多少 AI,而是把多少東西從 AI 移出去。


演進一:全 LLM Heartbeat(失敗)

最初的架構很直覺:OpenClaw 內建 heartbeat 機制,每小時叫醒 agent 做檢查。檢查清單包括 email、calendar、版本更新、sub-agent 殘留、cron 狀態等。

想法很美好:讓 agent 主動發現問題。

實際跑起來的問題:

  1. Token 浪費:agent 醒來要讀 context,花 token。90% 的時間回 HEARTBEAT_OK(沒事)。
  2. Session 衝突:偶爾 heartbeat cron 跟使用者對話搶 session,誰也進不去。
  3. Heartbeat skip:當 main session 閒置太久,OpenClaw 會跳過 cron,導致監控失效。

最致命的是:這是一個「越有用越浪費」的系統。監控項目越多,每次 heartbeat 的成本越高,但有事的機率並沒有相應增加。


演進二:bash + python + LLM 混合(不穩定)

第二版的想法:先用腳本收集資料,有異常才叫 LLM。

架構:routine-checks.sh → Python 分析 → 有事才 spawn isolated LLM session。

邏輯上說得通,但實作有問題:

  1. Python crash:型別不一致導致崩潰(JSON 裡的 epoch int vs ISO string,Python 處理時型別錯誤)
  2. 腳本邏輯散落各處:每次加新檢查都要改腳本 + 改 LLM prompt,維護困難
  3. 錯誤處理不一致:bash 的錯誤處理跟 Python 的錯誤處理邏輯不同,出錯時很難 debug

這個版本跑了幾週就因為 Python crash 壞掉,得手動重啟。對於一個「監控系統」來說,這是不可接受的。


演進三:Rust Binary + Type A/B 區分(目前)

第三版重新思考了核心問題:什麼事需要「理解力」,什麼事不需要?

核心原則:能固定就固定,不需要理解力的事不用 LLM。

把所有監控任務分為兩類:

Type A 監控型(固定邏輯判斷):

  • Calendar 衝突檢測:比較時間範圍,有重疊就報警
  • Sub-agent 殘留檢測:掃描 session 清單,超過 X 小時就清理
  • Repair loop 偵測:同一錯誤短時間內重複出現

這些都是固定邏輯,不需要 LLM「理解」什麼。有異常直接發 Telegram 通知。

Type B 分析型(需要理解力):

  • Email 分析:哪些 email 重要?需要立即回覆嗎?
  • Self-improvement 回顧:.learnings/ 裡累積的問題哪些該升級到 MEMORY.md?

這些需要「判斷」和「理解」,先收集資料,再 spawn isolated LLM 分析。

最終實作是一個 461KB 的 Rust binary,無異常時 <100ms(vs 舊版 ~20s)。

為什麼選 Rust?穩定性、無 runtime 依賴、錯誤處理嚴格。監控系統自己不能是不穩定因素。

// 偽代碼示意
fn main() -> Result<()> {
    let checks = vec![
        TypeACheck::CalendarConflict,
        TypeACheck::SubAgentFallback,
        TypeACheck::RepairLoop,
        TypeBCheck::EmailAnalysis,
        TypeBCheck::LearningsReview,
    ];
    
    for check in checks {
        match check {
            TypeACheck(check) => {
                if check.has_issue()? {
                    telegram_notify(&check.alert_message())?;
                }
            },
            TypeBCheck(check) => {
                let data = check.collect_data()?;
                if !data.is_empty() {
                    spawn_llm_analysis(data)?;
                }
            }
        }
    }
    
    Ok(())
}

Cron 遷移:OpenClaw → System Crontab

更進一步:連 OpenClaw 自己的 cron 都不用了。

Session-based cron 有固有問題:skip、session 衝突、announce 互搶。解法是直接用 system crontab,繞過 OpenClaw 的 session 機制。

當前的 cron 清單:

# 固定腳本(不需 LLM)
05 * * * *    routine-checks          # Rust binary
05 08 * * *   version-check.sh        # 比版本字串
10 08 * * *   morning-report.sh       # 每日報告
00 09 * * *   portfolio-report.sh     # 持倉報告
02 20 * * *   memory-janitor.sh       # 過期記憶清理

# 先收集再決定(需 LLM)
02 * * * *    memory-sync.sh          # 有對話才整理
02 20 * * *   daily-briefing.sh       # 收集後分析

設計原則圖:

需要做的事
  ├─ 固定邏輯? → bash/Rust 腳本 → 直接執行
  ├─ 需要判斷,但可以先收集資料? 
  │   → bash 收集 → 有資料才 spawn LLM
  └─ 需要即時互動? → 留在 main session

8 個 cron 裡,6 個完全不需要 LLM,2 個是先收集再決定。


Self-Improvement 系統

Routine-checks 解決了「怎麼省」,但 agent 還需要「怎麼學」。

想像一下:agent 犯了同一個錯誤三次,但每次都當作新問題處理,沒有「學習」。這不只是浪費,更是沒有進步。

Self-improvement 系統的核心是信號分類

三級信號

🔴 repair — 需要修復的問題

  • 指令/操作失敗 → ERRORS.md
  • 使用者糾正(「不對」「其實應該…」)→ LEARNINGS.md
  • 之前修好的問題又壞了 → ERRORS.md(regression)

🟡 optimize — 可以改進的地方

  • 知識過時或錯誤 → LEARNINGS.md
  • 發現更好的做法 → LEARNINGS.md(best practice)
  • 手動重複操作 2+ 次(應自動化)→ LEARNINGS.md(manual repeat)

🟢 innovate — 新需求

  • 使用者要求不存在的功能 → FEATURE_REQUESTS.md
  • 想做但目前做不到 → FEATURE_REQUESTS.md(capability gap)

格式規範

每條記錄格式:## [TYPE-YYYYMMDD-XXX] 標題 + Summary + Details + Suggested Action

關鍵欄位: recurring_count(同一模組/問題出現幾次,首次為 1)

提升規則: recurring_count ≥ 3 → 提升到 MEMORY.md 的 Agent Cases/Patterns

實際案例

案例一:Sub-agent 回傳機制

## [repair-20260228-001] Sub-agent 結果未送達使用者

**Summary:** Sub-agent 完成任務並 announce,但使用者沒收到結果
**Details:** Sub-agent 用 system event announce,但 main session 
正在處理其他對話,announce 被排隊延遲
**Suggested Action:** 改用 sessions_send 直接回傳
**recurring_count:** 3

這個問題出現 3 次後,被提升到 MEMORY.md 的 Agent Patterns:「Sub-agent 結果必須用 sessions_send 回傳,不依賴 announce 機制」。

案例二:Edit tool 匹配失敗

## [optimize-20260301-002] Edit tool 頻繁匹配失敗

**Summary:** 使用 edit tool 時,oldText 匹配失敗率高
**Details:** 主要是空白字元不一致(tab vs space),或複製時漏了換行
**Suggested Action:** 先 read 確認實際內容,再 edit
**recurring_count:** 4

4 次重複後提升為操作規範:「Edit 前必須 read 確認 exact match」。


兩個系統的交集

Self-improvement 和 routine-checks 形成閉環:

  1. Self-improvement 偵測到 manual_repeat(手動重複 2+ 次)
  2. 觸發自動化需求:這個流程應該腳本化
  3. Routine-checks 本身就是產物:從「手動檢查 email/calendar/版本」→ 記錄為 manual_repeat → 開發自動化腳本

結果:偵測問題 → 記錄 → 累積到閾值 → 程式化 → 減少 LLM 依賴。

最近的例子:「檢查 sub-agent 是否殘留」這個動作手動做了 4 次,記錄為 manual_repeat。結果:寫進 routine-checks binary,變成每小時自動檢查。


當前架構全貌

整個系統現在長這樣:

System Crontab
  ├─ routine-checks (Rust binary, 每小時)
  │   ├─ Type A: 固定邏輯 → 直接 Telegram 通知
  │   └─ Type B: 先收集 → 有事才 spawn LLM
  ├─ 其他固定腳本 (6個)
  │   └─ 版本檢查、portfolio 報告等
  └─ 條件性 LLM (2個)
      ├─ memory-sync: 有對話才整理記憶
      └─ daily-briefing: 收集 email/社群後分析

Self-improvement 系統平行運行:

對話中偵測信號 → 記錄到 .learnings/
recurring_count ≥ 3 → 提升到 MEMORY.md
manual_repeat 類型 → 觸發自動化 → 進入 routine-checks

學到的事

反直覺的結論

AI agent 成熟的標誌不是用了多少 AI,而是把多少東西從 AI 移出去。

類比:好的工程師不是寫最多 code 的,而是知道什麼時候不該寫 code。

LLM 應該只做需要「理解力」的事——分析、創意、判斷。其他一切:grep、find、curl、Rust binary。

三個設計原則

  1. 能固定就固定:不浪費 LLM token 做 grep/find/比較
  2. 先收集再決定:資料收集用腳本,只在需要理解力時才呼叫 LLM
  3. Type A/B 區分:監控型(固定邏輯)vs 分析型(需 LLM)

為什麼這樣重要

不只是省錢,更是可靠性。LLM 再強,也會有 latency、rate limit、偶爾的奇怪回覆。對於監控系統來說,461KB 的 Rust binary 比任何 LLM call 都可靠

Self-improvement 系統確保 agent 不只是「聰明」,還會「學習」。同樣的坑不踩第二次,同樣的手動流程不重複第三次。


下一步

目前的 routine-checks 還有提升空間:

  1. 預測性監控:不只是「有事才報」,而是「可能有事就預警」
  2. Agent health metrics:token 使用量、錯誤率、回應時間的趨勢分析
  3. 更細緻的 Type B 收集:針對不同類型的分析任務,收集更精確的資料

但核心原則不會變:盡量不用 AI,該用 AI 時才用 AI。


這是 OpenClaw 系列的第三篇。前一篇是記憶管理架構。Self-improvement 系統正在運作中。