iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0
AI Engineering

生活中的 AI 應用:我在家用 NAS 養了一隻 Agent,幫我看盤、顧家、盯備考——30 天自架實錄系列 第 15

Day 15:一個系統,兩個排程器——後來我把大半的 AI 拿掉了

  • 分享至 

  • xImage
  •  

補記(發文當日):這篇寫成時 NAS 上是 36 個 job,現在是 57 個——其中一半以上是後來補的「檢查別人有沒有好好上班」的 job。更重要的是,這半年我把大部分 job 的 LLM 拿掉了:現在 57 個裡有 42 個是零 LLM 的純腳本。(這個數字每隔幾週就變,我不打算再追著改——重點是比例。)原因寫在本篇後半段,那可能是這個系列最反直覺的一段。

早上 8:00,Telegram 跳出晨報:今天的行程、天氣、該記得的事。白天,系統自己檢查行情快取是不是新鮮的。22:00,它把今天的收尾報告悄悄送出。凌晨 3:00,Agent 本該「睡覺」整合記憶(Day 9 拆過台:它其實賴床到上午 11:00,還沒搬回來);凌晨 4:00 抓美股收盤,清晨 6:00 趁我起床前把持倉損益同步好。

這一整天,我沒有下過任何一個指令。

支撐這一切的是「排程」。但講排程之前,得先講一件容易被忽略、卻決定了整個系統形狀的事:我的排程不是一套,是兩套,跑在兩台完全不同的機器、兩種完全不同的計價上。 這一篇的主角是其中一套,但要看懂它為什麼長這樣,得先把兩套一起攤開。

先說清楚:兩個排程器,一條分界線

我的系統有兩個會「自己醒來做事」的地方:

NAS 側排程器 訂閱側排程器
跑在哪 家裡 NAS 的 Agent Runtime(Cron Scheduler) 我平常跟 AI 一起工作的那個訂閱環境(大腦那側,見 Day 1)
計價 按次計費——每醒來一次就跳一次表 定額訂閱——想跑多久都不加價
掛著什麼 57 個 job:抓價、算損益、發通知、健檢、自癒 十幾個「需要真思考」的定期任務:選股週報、深度分析、生活覆盤
該有多聰明 越笨越好——多數是純腳本 這裡才需要開放式思考

這條分界線是整個系列的隱藏骨架。Day 1 講過「大腦在訂閱端、Clawrion 是家裡的持久化系統」——落到排程層,就是這張表:把需要思考的定期任務放在定額側,把確定性的執行留在按次側。

https://ithelp.ithome.com.tw/upload/images/20260815/20182865VNmnsfraRk.png

這一篇兩欄都講。左邊的 NAS 側掛著 57 個 job,是我半年來一個一個長出來、在時區和「殭屍 job」上繳過學費的地方,故事多、篇幅也重;右邊的訂閱側跑著十幾個需要思考的定期任務,機制簡單,但角色關鍵,等下有一整段專門走它。這半年我做的最大一件事,就是在這兩欄之間搬東西——把需要思考的往右搬、把確定性的留在左邊、甚至乾脆把左邊的 AI 拿掉。所以與其說今天講排程,不如說講的是這條分界線本身:一個定期任務進來,我怎麼決定它該落在哪一欄。先從左邊那個 war story 比較多的開始。

NAS 側的 57 個 job 長什麼樣:先分類再管理

任務一多,第一件事不是優化,是分類。NAS 上的 job 大致落在四個籃子:

類型 例子 特性
報告生成 投資晨報、生活簡報、週回顧 讀資料 → 生成文字 → 推播
資料同步 行情快取更新、持倉價格同步 需要執行程式(exec),不太需要「聰明」
健康巡檢 快取新鮮度檢查、部署驗收、session 肥大偵測 平常沉默,異常才說話
記憶整合 每日 dreaming、記憶歸檔、session 壓縮 凌晨離峰跑,吃 token 較兇

分類的價值在於:同一類 job 適用同一套規則。報告生成類可以用免費模型;資料同步類必須用支援工具呼叫的模型(這是 Day 23 的主題);健康巡檢類的原則是「沒事不要吵我」;記憶整合類統一排在凌晨,避開我真的在用系統的時段。

NAS 側的一天:排程時間軸

把主要任務攤開,一天大概是這樣(台北時間):

時間 任務 類型
03:00(表定;實跑 11:00,Day 9) Dreaming 記憶整合 記憶整合
03:50 Session 大小巡檢(過胖自動壓縮) 健康巡檢
04:00 美股收盤 → 更新行情快取(同一班一起帶台股收盤價) 資料同步
04:10 Session 清潔工(歸檔 >7 天舊檔) 健康巡檢
04:30 部署驗收(md-server 健康檢查) 健康巡檢
06:00(週二–六) 持倉損益同步 資料同步
08:00 投資晨報 報告生成
每 2 小時(全天) 環境巡檢(溫濕度) 健康巡檢
22:00 每日收尾報告 報告生成
每小時 Cron 健康守衛(檢查 job 有沒有活著) 健康巡檢

注意一個設計:離峰做重活,尖峰只做輕活。凌晨 3:00–5:00 是記憶整合和清理的黃金時段,因為沒人在用系統,跑掛了也有時間自癒;白天的 job 則盡量短小,單一任務、單一輸出。

半年後的實話:這 57 個裡,四分之三根本不叫 LLM

寫這篇的時候是 36 個 job,發文的今天是 57 個,而更值得講的是這個比例:

57 個排程任務裡,42 個是零 LLM 的純腳本。真的會呼叫模型的只有 15 個。

也就是說,這套「AI Agent 系統」的 NAS 側,有超過七成的排程跟 AI 沒有任何關係——它們是 Node 和 Python 腳本,讀檔、算數、發訊息,沒有任何一個 token 被消耗。

而剩下那 15 個「會用模型」的,也不是你想的那樣。它們的模型分佈長這樣:

模型層 幾個 拿來做什麼
免費額度的輕量模型 7 固定格式的通知與報告文案
便宜的付費小模型 8 需要跑 shell、讀檔、依規則判斷的自動化
旗艦模型 0 ——它完全不出現在 NAS 排程裡

所以精確的說法不是「我把 AI 拿掉了」。我拿掉的是「貴的 AI」,以及「沒人看著的開放式思考」——這兩件事一起被請出了 NAS 排程層。

那 15 個仍然在用模型的,也被綁得很緊:輸入固定、輸出格式固定、任務邊界窄到即使模型答歪了,下游也接得住。

這個比例不是退步,是我半年來最重要的一次轉向——而它的另一半,就在訂閱側那個排程器上。

為什麼把 LLM 從 NAS 側拿掉

早期我幾乎每個 job 都掛模型,理由聽起來很正當:「讓它自己判斷比較彈性。」實際跑起來,彈性變成了三個問題:

輸出不穩定。 同一份資料,今天的摘要有五段、明天有三段,格式偶爾換一種。下游要解析它就得寫一堆容錯。

故障變得難查。 腳本壞了會噴 stack trace;LLM 「壞了」只是回答得怪怪的,你得肉眼看才發現,而多數時候沒人在看。

花錢。 這點最直接——而且是按次花。NAS 側每醒來一次就跳一次表,後面那筆一夜 40 美金的學費就是這麼燒的(Day 22)。

回頭看,那些 job 的工作內容其實是確定性的:抓價格、算損益、比對閾值、組一段固定格式的文字。這些事情答案完全由資料決定——既然如此,讓語言模型「重新想一次」除了增加不確定性和成本,沒有帶來任何東西。

還有一個推力我一開始沒意識到:**計價結構本身就把架構往這裡推。**訂閱定額側想多久都不加價、NAS 按次側每醒來一次跳一次表;當「有人在場、或跑在定額側」的思考邊際成本趨近於零,「讓 NAS 半夜自己想」就既貴又不可控(沒人在看)。於是我做了件反直覺的事:把思考往定額側集中,NAS 這側榨到只剩執行——Day 1 講的「大腦在訂閱端」,落地就是這 42 支純腳本。

改寫它們本身就是最好的例子:把幾十支腳本從「掛模型」改成「純執行」,在以前是個大工程,現在是我講清楚每支要做什麼、AI 助手實作、我逐支讀過驗收。判準很單純:

輸出能不能由輸入唯一決定?能,就不該用 LLM。

現在 NAS 側還掛著模型的那 15 個裡,真正需要「講人話」的是少數——每日能量早報、碩士備考週報這類,共同點是輸出沒有標準答案;其餘是需要呼叫工具、但判斷有規則可循的執行類。

那 AI 到底用在哪:兩個排程器的分工,加上第三個不是排程的地方

這是我覺得整個系列最值得講、卻最容易被誤會的一段。

如果你只看 NAS 上這 57 個 job,會以為有個聰明的 AI 在後面統籌一切。事實相反——它們絕大多數是笨的,而且我刻意讓它們笨。 真正需要思考的排程沒有消失,它搬家了:搬到訂閱側那個定額排程器上。

最好的證據是某次盤點時發現,同一個「生活週回顧」兩邊各掛了一份——NAS 側一份、訂閱側一份。被我砍掉的是按次計費那份,留下定額側那份。同一件需要思考的事,我選了不會因為它多想幾次就跳表的那個排程器。

所以完整的分工其實有三層,兩層是排程、一層不是:

角色 跑在哪 做什麼 用不用 LLM
NAS 排程(多數) 按次側 每天重複、答案確定的事 ❌ 不用
NAS 排程(15 個) 按次側 需要組織語言或工具呼叫、但邊界綁很緊的事 ✅ 用便宜的
訂閱側排程(十幾個) 定額側 需要真思考的定期任務:週報、深度分析、覆盤 ✅ 用,不怕它多想
人機配對(我 + AI 助手) 定額側,即時 開發、除錯、重構——我定方向與驗收,它負責實作 ✅ 這裡才是主場

前三列是「自己會醒來」的排程,最後一列是「我主動開對話」的即時協作——它不是排程,但它是這半年系統每一次演進的來源:新增監控、修 bug、重構腳本、追詭異故障,都是我開個對話視窗,跟 AI 助手一起讀碼、假設、驗證,再把結論變成腳本部署上去。

這裡有個容易被忽略的反諷:AI 在這套「AI 系統」裡最大的貢獻,是幫我寫出一堆不需要 AI 的腳本——那些腳本後來安安靜靜跑在按次側,一個 token 都不燒。

如果要我用一句話總結半年的心得:別讓 AI 做腳本就能做的事,把它留給只有它能做的事——而那件事,要嘛需要你也在場,要嘛就放到不會因為它多想而跳表的那個排程器上。

右邊那一欄:訂閱側排程器實際在跑什麼

講到這裡,右邊那一欄不能再只當背景板了。具體說,它就是 Claude 桌面版的排程任務(Desktop Scheduled Tasks)——我把一個個 prompt 寫成任務,掛在訂閱環境(Day 1 說的「大腦」)上定時醒來做事。這裡誠實補一刀:這種桌面排程跑在我自己開著的電腦上、不是雲端,得靠機器醒著——不像左邊那台 24 小時的 NAS(Claude 另有關機也照跑的雲端 Routines,但我這批用的是桌面版)。撇開這點,它跑的十幾個定期任務,跟左邊是完全不同的物種。

它跑什麼。 都是「答案不由資料唯一決定」的事。我在定額側掛了十幾個這種定期任務,舉幾個實際在跑的:

任務 它在做的「思考」
每週選股 讀財報、比較同業、寫出「為什麼是這幾檔」的理由——不是查價格
投資組合週反思 / 月度循環 回看這週部位變化、檢討我自己的決策,而不只是把數字重報一次
台股槓桿檢查 判斷目前槓桿在不在我能承受的範圍,是風險判斷不是算術
AI 資本支出新聞監控 追產業動態、判斷哪幾條跟我的持倉有關
生活與系統月回顧 把一個月的家居、健康、系統事件收斂成一份給我讀的覆盤

這些任務的輸出天生就該長得不一樣——這週看多、下週看空,是判斷變了,不是 bug。放在左邊那個「格式固定、產物要餵給下游腳本」的世界裡它會格格不入;放在右邊,非確定性本身就是它的價值。這也是為什麼它們配得上定額側那顆更強的腦:值得它多想,也不怕它多想。

為什麼在這。 除了計價(前面說過,定額側不怕它多想),還有「有沒有人接得住」:左邊的 job 產物是餵給下游腳本的,格式錯了會炸;右邊的產物是給我讀的分析,語氣重一點、結構換一種,我自己消化,容錯高得多。

兩個排程器怎麼交接——這是最容易漏講的一環。 它們不是各跑各的兩座孤島。訂閱側想完、寫出一份分析之後,會落成一份交接摘要放進共用的資料夾;NAS 側每天凌晨的記憶整合 job(就是那個賴床到 11 點的 dreaming)會去讀它、消化進長期記憶。反過來,NAS 側每天累積的事實——持倉現價、告警、job 成敗——也是訂閱側下次思考時的輸入。一邊負責「想」、一邊負責「記與執行」,中間靠一份兩邊都會讀寫的檔案接起來。

右邊的坑少:它的執行環境(Claude 這個 app)是別人做好的,我不需要像左邊那樣管容器有沒有活、時區有沒有寫錯、會不會半夜 OOM——那些學費全繳在 NAS 那側;它唯一的代價是得靠我電腦開著(桌面排程、不是雲端),好在我電腦本來就常開。右邊我只需要決定「什麼該搬過去」,左邊我得決定「搬過來的怎麼不出事」。 這篇剩下的內容,都是後者。

一個 job 該長什麼樣

NAS 側的 Cron Scheduler 裡,每個 job 是一份 JSON 設定。它的形狀本身就藏著這篇的主線——payload.kind 這個欄位,決定它是「會思考」還是「純執行」:

// 一個「會用模型」的 job(15 個之一)
{
  name: "fortune-daily-energy-check",      // 用途一眼看懂,別叫 job-17
  schedule: {
    kind: "cron",
    expr: "30 6 * * *",
    tz: "Asia/Taipei"        // ⚠️ tz 一定要寫!不寫就默默當 UTC 跑,還不報錯
  },
  payload: {
    kind: "agentTurn",       // ← 會思考的 job
    model: "google/gemini-3-flash-preview",  // 模型在 payload 裡,不在頂層
    message: "……(prompt)",
    fallbacks: []            // 在 payload 裡,必須為空——Day 22 會用 $40 解釋為什麼
  },
  delivery: {
    mode: "announce",
    to: "<GROUP_ID>",
    threadId: 29             // 主題頻道(命理)
  }
}

而那 42 個純腳本,只是把 payload 整包換掉——kind: "command"、一個 argv 直接跑 shell,連 model 欄位都沒有:

payload: {
  kind: "command",
  argv: ["sh", "-lc", "python3 .../portfolio_price_sync.py"],
  timeoutSeconds: 120
}

光是這個 payload.kind 的分岔,就是前半篇的縮影:agentTurn 會燒 token、command 不會。我把某個 job「拿掉 AI」,具體動作就是把它的 payload 從 agentTurn 改成 command——換一個 kind、附一支腳本,一個 token 都不再燒。

逐欄講我的取捨:

schedule.tz 一定要顯式宣告。 這是我踩過最蠢也最痛的坑。排程器的預設時區是 UTC——你不寫 tz,它不報錯,只會默默把「台北早上 5 點」當成「UTC 早上 5 點」跑,也就是台北下午 1 點。我曾經漏掉 tz,美股收盤抓取晚了 8 小時進快取,晨報連續兩天引用前一天的舊價。更陰險的是跨日:台北的凌晨任務在 UTC 是「前一天」,如果邏輯裡有「今天日期」的字串拼接,檔名會差一天。系統其實支援直接寫 tz: "Asia/Taipei"(上面的範例就是)——所以正確的紀律不是「自己心算 UTC」,是每個 job 都明寫 tz,別讓它吃預設值

model 是成本開關,而且它藏在 payload 裡。 純文字生成用免費模型,需要執行程式的用低價付費模型,旗艦模型在排程裡一律禁用(Day 22 有血淚背景)。這個欄位的位置我特別點出來,因為它後來咬過我一次——有個合規掃描去讀「頂層的 model」,而 model 根本在 payload.model,於是那個掃描從上線第一天起就掃不到任何違規,看起來永遠合格(Day 20 的故事)。

timeout 在 payload.timeoutSeconds,是保險絲。 LLM 任務偶爾會卡住(API 慢、工具呼叫迴圈),沒有 timeout 的 job 會一直佔資源、在按次側一直燒錢。通則是 ≤ 120 秒,超過就該懷疑任務設計有問題,而不是調大數字。(誠實說:上面那支 fortune 範例我其實漏了 timeout——缺 timeout 正是每週對帳器會揪出來的違規之一。該設而沒設,是我自己的待辦,不是它不重要。)

delivery.threadId 要指向真實存在的頻道。 這句話聽起來像廢話,直到你遇到「靜默失敗」。

我踩過的坑:殭屍 job——活著,但沒人收到屍體通知

最難抓的故障不是 job 報錯,是 job「成功執行、但輸出去了不存在的地方」。

我的 Telegram 通知用群組的主題(topic)功能分頻道:投資一個、家居一個、備考一個。某天我建了一個新 job,隨手把 delivery.threadId 設成一個看起來合理的數字——那個 topic 根本不存在。結果是:job 每天準時跑、LLM 每天認真生成報告、API 費每天照扣,而訊息從來沒有送達過任何地方。Bot 對不存在的 topic 發文不會噴錯誤,就這樣沉默地空轉了快兩週,直到我某天想「咦,那個提醒怎麼很久沒看到了」。

這種「殭屍 job」教會我兩件事:

  1. 有效值要白名單化。 我把「合法的 threadId 清單」寫進行為協定檔,新增 job 前必須對照。靠記憶會忘,靠文件才可靠。
  2. 要有 job 檢查 job。 系統裡有一支每小時跑的健康守衛腳本,直接讀排程的執行紀錄:該跑的沒跑、連續失敗、輸出異常,都會發告警。後來又加了每日的自我掃描 job,專門掃「設定違規」——model 用錯、topic 不合法、fallbacks 沒清空、時區沒宣告,一律列出來(至於缺 timeout 這種,是每週的對帳器另外抓)。監控排程的排程,聽起來很套娃,但幾十個 job 靠人眼盯是不現實的——這也是為什麼那 57 個裡有一半是「檢查別人有沒有好好上班」的 job。

另一個小坑:job 會默默增生。實驗性任務建了忘記刪、功能重疊的 job 各跑各的(訂閱側那份「生活週回顧」就是這麼被我抓到重複的)。現在我每月盤點一次清單,原則是「說不出存在理由的 job 就砍」——實際上多半是直接刪(上一輪盤點就移掉了十幾個);少數拿不準的才先停用、觀察一陣子再說。

小結+明日預告

排程編排的核心不是 cron 語法,是兩件事:一是治理——分類、白名單、保險絲、還有監控排程本身的排程;二是分流——想清楚每個定期任務該跑在哪個排程器上。答案由計價和「需不需要思考」共同決定:確定性的執行留在 NAS 按次側、還能的話連 AI 都拿掉;需要開放式思考的,搬到訂閱定額側去,不怕它多想。

明天來拆這張時間軸裡最操勞的一個環節:凌晨 4 點的行情快取。免費 API 的世界裡,抓太兇會被 ban,抓太慢資料過期——我用 300ms 的延遲在鋼索上找平衡。


🔑 這篇的關鍵字
兩個排程器:NAS 按次側(執行為主、能拿掉 AI 就拿掉)× 訂閱定額側(承接需要思考的定期任務)· 分流準則:計價 × 需不需要開放式思考 · cron 排程編排治理 · schedule 存 UTC,一定要明寫時區(設定檔每行後面註解台北時間)· 殭屍 job 偵測 · fallbacks: [](失敗就失敗,別讓故障自動變貴)· 判準:輸出能否由輸入唯一決定?能,就不該用 LLM · job 檢查 job(健康守衛 + 設定違規掃描)


我是一名金融業資訊工程師,這是我半年來在家自架 AI Agent 系統的實錄。


上一篇
Day 14:第二週小結——給 AI 裝記憶的五個設計模式
下一篇
Day 16:免費抓行情的代價——限速 300ms 與被 ban 的距離
系列文
生活中的 AI 應用:我在家用 NAS 養了一隻 Agent,幫我看盤、顧家、盯備考——30 天自架實錄22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言