補記(發文當日):這篇寫成時 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 是家裡的持久化系統」——落到排程層,就是這張表:把需要思考的定期任務放在定額側,把確定性的執行留在按次側。

這一篇兩欄都講。左邊的 NAS 側掛著 57 個 job,是我半年來一個一個長出來、在時區和「殭屍 job」上繳過學費的地方,故事多、篇幅也重;右邊的訂閱側跑著十幾個需要思考的定期任務,機制簡單,但角色關鍵,等下有一整段專門走它。這半年我做的最大一件事,就是在這兩欄之間搬東西——把需要思考的往右搬、把確定性的留在左邊、甚至乾脆把左邊的 AI 拿掉。所以與其說今天講排程,不如說講的是這條分界線本身:一個定期任務進來,我怎麼決定它該落在哪一欄。先從左邊那個 war story 比較多的開始。
任務一多,第一件事不是優化,是分類。NAS 上的 job 大致落在四個籃子:
| 類型 | 例子 | 特性 |
|---|---|---|
| 報告生成 | 投資晨報、生活簡報、週回顧 | 讀資料 → 生成文字 → 推播 |
| 資料同步 | 行情快取更新、持倉價格同步 | 需要執行程式(exec),不太需要「聰明」 |
| 健康巡檢 | 快取新鮮度檢查、部署驗收、session 肥大偵測 | 平常沉默,異常才說話 |
| 記憶整合 | 每日 dreaming、記憶歸檔、session 壓縮 | 凌晨離峰跑,吃 token 較兇 |
分類的價值在於:同一類 job 適用同一套規則。報告生成類可以用免費模型;資料同步類必須用支援工具呼叫的模型(這是 Day 23 的主題);健康巡檢類的原則是「沒事不要吵我」;記憶整合類統一排在凌晨,避開我真的在用系統的時段。
把主要任務攤開,一天大概是這樣(台北時間):
| 時間 | 任務 | 類型 |
|---|---|---|
| 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 則盡量短小,單一任務、單一輸出。
寫這篇的時候是 36 個 job,發文的今天是 57 個,而更值得講的是這個比例:
57 個排程任務裡,42 個是零 LLM 的純腳本。真的會呼叫模型的只有 15 個。
也就是說,這套「AI Agent 系統」的 NAS 側,有超過七成的排程跟 AI 沒有任何關係——它們是 Node 和 Python 腳本,讀檔、算數、發訊息,沒有任何一個 token 被消耗。
而剩下那 15 個「會用模型」的,也不是你想的那樣。它們的模型分佈長這樣:
| 模型層 | 幾個 | 拿來做什麼 |
|---|---|---|
| 免費額度的輕量模型 | 7 | 固定格式的通知與報告文案 |
| 便宜的付費小模型 | 8 | 需要跑 shell、讀檔、依規則判斷的自動化 |
| 旗艦模型 | 0 | ——它完全不出現在 NAS 排程裡 |
所以精確的說法不是「我把 AI 拿掉了」。我拿掉的是「貴的 AI」,以及「沒人看著的開放式思考」——這兩件事一起被請出了 NAS 排程層。
那 15 個仍然在用模型的,也被綁得很緊:輸入固定、輸出格式固定、任務邊界窄到即使模型答歪了,下游也接得住。
這個比例不是退步,是我半年來最重要的一次轉向——而它的另一半,就在訂閱側那個排程器上。
早期我幾乎每個 job 都掛模型,理由聽起來很正當:「讓它自己判斷比較彈性。」實際跑起來,彈性變成了三個問題:
輸出不穩定。 同一份資料,今天的摘要有五段、明天有三段,格式偶爾換一種。下游要解析它就得寫一堆容錯。
故障變得難查。 腳本壞了會噴 stack trace;LLM 「壞了」只是回答得怪怪的,你得肉眼看才發現,而多數時候沒人在看。
花錢。 這點最直接——而且是按次花。NAS 側每醒來一次就跳一次表,後面那筆一夜 40 美金的學費就是這麼燒的(Day 22)。
回頭看,那些 job 的工作內容其實是確定性的:抓價格、算損益、比對閾值、組一段固定格式的文字。這些事情答案完全由資料決定——既然如此,讓語言模型「重新想一次」除了增加不確定性和成本,沒有帶來任何東西。
還有一個推力我一開始沒意識到:**計價結構本身就把架構往這裡推。**訂閱定額側想多久都不加價、NAS 按次側每醒來一次跳一次表;當「有人在場、或跑在定額側」的思考邊際成本趨近於零,「讓 NAS 半夜自己想」就既貴又不可控(沒人在看)。於是我做了件反直覺的事:把思考往定額側集中,NAS 這側榨到只剩執行——Day 1 講的「大腦在訂閱端」,落地就是這 42 支純腳本。
改寫它們本身就是最好的例子:把幾十支腳本從「掛模型」改成「純執行」,在以前是個大工程,現在是我講清楚每支要做什麼、AI 助手實作、我逐支讀過驗收。判準很單純:
輸出能不能由輸入唯一決定?能,就不該用 LLM。
現在 NAS 側還掛著模型的那 15 個裡,真正需要「講人話」的是少數——每日能量早報、碩士備考週報這類,共同點是輸出沒有標準答案;其餘是需要呼叫工具、但判斷有規則可循的執行類。
這是我覺得整個系列最值得講、卻最容易被誤會的一段。
如果你只看 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 那側;它唯一的代價是得靠我電腦開著(桌面排程、不是雲端),好在我電腦本來就常開。右邊我只需要決定「什麼該搬過去」,左邊我得決定「搬過來的怎麼不出事」。 這篇剩下的內容,都是後者。
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「成功執行、但輸出去了不存在的地方」。
我的 Telegram 通知用群組的主題(topic)功能分頻道:投資一個、家居一個、備考一個。某天我建了一個新 job,隨手把 delivery.threadId 設成一個看起來合理的數字——那個 topic 根本不存在。結果是:job 每天準時跑、LLM 每天認真生成報告、API 費每天照扣,而訊息從來沒有送達過任何地方。Bot 對不存在的 topic 發文不會噴錯誤,就這樣沉默地空轉了快兩週,直到我某天想「咦,那個提醒怎麼很久沒看到了」。
這種「殭屍 job」教會我兩件事:
另一個小坑:job 會默默增生。實驗性任務建了忘記刪、功能重疊的 job 各跑各的(訂閱側那份「生活週回顧」就是這麼被我抓到重複的)。現在我每月盤點一次清單,原則是「說不出存在理由的 job 就砍」——實際上多半是直接刪(上一輪盤點就移掉了十幾個);少數拿不準的才先停用、觀察一陣子再說。
排程編排的核心不是 cron 語法,是兩件事:一是治理——分類、白名單、保險絲、還有監控排程本身的排程;二是分流——想清楚每個定期任務該跑在哪個排程器上。答案由計價和「需不需要思考」共同決定:確定性的執行留在 NAS 按次側、還能的話連 AI 都拿掉;需要開放式思考的,搬到訂閱定額側去,不怕它多想。
明天來拆這張時間軸裡最操勞的一個環節:凌晨 4 點的行情快取。免費 API 的世界裡,抓太兇會被 ban,抓太慢資料過期——我用 300ms 的延遲在鋼索上找平衡。
🔑 這篇的關鍵字
兩個排程器:NAS 按次側(執行為主、能拿掉 AI 就拿掉)× 訂閱定額側(承接需要思考的定期任務)· 分流準則:計價 × 需不需要開放式思考 ·cron排程編排治理 · schedule 存 UTC,一定要明寫時區(設定檔每行後面註解台北時間)· 殭屍 job 偵測 ·fallbacks: [](失敗就失敗,別讓故障自動變貴)· 判準:輸出能否由輸入唯一決定?能,就不該用 LLM · job 檢查 job(健康守衛 + 設定違規掃描)
我是一名金融業資訊工程師,這是我半年來在家自架 AI Agent 系統的實錄。