人工智慧與自動化 2026年4月22日

2026:在租用的 Mac mini 上,用 launchd 為 OpenClaw 設定 ulimit、記憶體與 CPU 安全線

ProxyMac 工程團隊 2026年4月22日 約 12 分鐘閱讀

香港、日本、韓國、新加坡或美國ProxyMac Mac mini M4 上跑 OpenClaw 時,最常見的「假死」不是行程崩潰,而是常駐記憶體超過 launchd 預期能承受的範圍:介面上仍顯示執行中,但通道轉接器開始拒收工作,因為核心換頁的速度快過你的探測間隔。營運團隊往往先怪模型品質,真正的根因卻是預設 ulimit -n 只有 256 這類未宣告的限制,或是 MCP 工具伺服器 在尖峰時大量 fork 子行程造成的行程數上限撞牆。

這篇 2026 指南會先整理默默 OOM 的特徵,再對照互動式 shell 與 LaunchAgent 的兩套 ulimit 世界,列出三組建議的可觀測門檻(例如實體記憶體七成以下的 headroom、一萬個開啟檔案的上限觀察、以及每小時兩 GB級別的 swapin 警訊),並提供可直接貼進 plist 的 SoftResourceLimits 片段。最後把上限調整與 並行代理 以及 健康探測 串起來,讓你在使用者察覺前就收到分頁。

若你要從日誌反查根因,請搭配 部署疑難排解說明中心;當問題不是打錯字而是架構吃不下的時候,再用 定價 把記憶體層級升上去,會比無限調高 ulimit 更誠實。

為什麼這些細節在雲端 mini 特別值得寫成文章?因為無人值守的自動化最怕「看起來還活著」。在筆電上你能聽到風扇狂轉、能看到視窗變慢;在遠端伺服器上,唯一真相是度量與統一日誌。把資源上限寫進版本化的 plist,等於把「這臺機器允許長成多大」變成可稽核的設定,而不是某位工程師 ~/.zprofile 裡的隱性契約。

默默 OOM 與上限訊號:先記錄再怪模型

macOS 的統一日誌很容易把 jetsam 相關事件埋在資訊層級的雜訊裡。相對實用的現場訊號是:工具延遲的 p95 突然變成四倍以上,但 CPU 使用率卻長期低於 40%。這種「慢但不熱」的形狀,幾乎可以直接指向記憶體壓力,而不是 LLM 推理變慢。另一個常見線索是 MCP stdio 橋接器噴 ENOTFILE 或「開啟檔案過多」:當並行代理同時拉高連線數時,256 個描述詞很快就會見底。

建議你在 OpenClaw 的 wrapper 腳本開頭就印出當下的 ulimit 與可用記憶體摘要,並把輸出附在每次釋出的變更紀錄裡。這樣當兩週後的 regression 出現時,你不用猜當時的環境是否一致;你會有明確的文字證據指出 launchd 與本機測試是否同一套上限。

  • 壓縮器與 swapin:在 16 GB 的 SKU 上,若每小時持續超過 500 MB 的 swapin,代表你已經離舒適圈很遠。
  • LaunchAgent 的 throttleReason:若字串提到 resource-limits,請把對應 plist 版本記進 git,而不是只在 Slack 裡口頭交代。
  • 本地閘道出現大量 502:常見於 worker 子行程無法 fork,背後可能是 RLIMIT_NPROC

另外一個容易被忽略的訊號是「錯誤型態突然從逾時變成連線重設」。當記憶體壓力讓系統開始積極回收分頁時,某些長連線會先變得不穩定,接著才輪到行程被 jetsam。把 TCP 層與應用層的錯誤分開標記,會讓你在 Grafana 或簡單的 syslog 匯總裡更快定位。

launchd 對照互動式 shell:兩套 ulimit 世界

透過 SSH 登入時,~/.zprofile 可能已經幫你執行 ulimit -n 65535;但 LaunchAgent 不會自動複製這段行為。請務必在plist 實際呼叫的 wrapper 腳本裡列印限制,而不是從你筆電上的互動式 shell 複製貼上輸出。

情境常見 maxfiles誰設定風險
SSH 登入10240 以上Shell 設定檔除錯 launchd 時會誤導
LaunchAgent 預設256–1024系統預設MCP 尖峰很容易耗盡
宣告 SoftResourceLimits 之後8192–65535你的營運團隊必須搭配程式端的 FD 衛生

若你同時使用 GUI 與背景代理,請留意 macOS 會依使用者層級拆分 domain。把 OpenClaw 放在 gui/$(id -u) 底下的 LaunchAgent,與放在系統層級的 Daemon,預設上限可能不同;複製 plist 時不要只改標籤而不改上下文。對團隊而言,最安全的做法是:每個環境各有一份「上限基線」文件,並在 CI 裡用 plistlint 或簡單的 xmllint 做結構檢查,避免手滑把整數寫到錯的巢狀字典裡。

可觀測門檻:什麼數字值得把人叫醒

你不需要為了 OpenClaw 另外架一套重量級 APM;在既有的 健康探測 腳本裡,每分鐘追加三個數字寫進 syslog 就很有用:可用分頁檔案描述詞數量、以及壓縮器分頁。當連續兩次取樣都跨過門檻,再呼叫 on-call,能顯著降低誤報。

規則組合(16 GB SKU 起點):可用 RAM 低於 2.5 GB、開啟 FD 超過 8000、或壓縮器分頁超過 120000。若你租的是 24 GB 機型,請依比例調整,而不是直接沿用同一組絕對值。

把門檻寫成「比例加絕對值」的混合式通常最穩:比例用來吃掉不同 macOS 小版本之間的基線差異,絕對值用來防止在小型 SKU 上被百分比騙過去。舉例來說,七成記憶體的 headroom 在 16 GB 與 24 GB 上對應的絕對空間不同;你的告警應該兩邊都看得懂。

Plist 片段:用 SoftResourceLimits 避免把 launchd 一起拖下水

請把鍵放在代理程式字典底下,而不是全域 domain。硬上限建議維持在軟上限的 1.25 倍以內,讓設定錯誤時能快點失敗,而不是進入長時間卡死。

<key>SoftResourceLimits</key> <dict> <key>NumberOfFiles</key><integer>8192</integer> <key>NumberOfProcesses</key><integer>512</integer> <key>Stack</key><integer>8388608</integer> </dict>

重載時請使用 launchctl kickstart -k gui/$(id -u)/com.example.openclaw,但務必先保留第二條 SSH 工作階段;這種操作紀律可以直接沿用 閘道重啟與復原 裡的流程,避免把自己鎖在門外。若你會在非尖峰時段做 rolling restart,也請把「誰負責監看第一個探測成功」寫進輪值表,而不是假設自動化永遠會自我修復。

並行代理指南 裡的每一條代理,都會複製模型控制代碼與 MCP 連線。若實驗室量測顯示把並行加倍會增加 3.2 GB RSS,就不要順手把 NumberOfFiles 也加倍「以防萬一」:那只會掩蓋描述詞外洩。正確順序是先降並行,直到 RSS 在 macOS 基線服務之後仍能落在實體記憶體的 70%以下,再評估是否需要硬體升級。

當你把 worker 數、模型快取、以及 MCP 子行程數寫成表格時,通常會發現瓶頸是「某一層的倍率」而不是單一參數。把倍率畫成簡單的線性外推,有助於向非技術利害關係人解釋為什麼「加一個代理」不是加一個執行緒那麼便宜。

密鑰備註:RLIMIT_MEMLOCK 盲目拉高很少能救 OpenClaw;請依 密鑰與鑰匙圈強化 管理秘密,而不是用 mmap 大快取去換不穩定的上限。

常見問題

若需 8G/16G 檔位記憶體預算、活動監視器與端側大模型調度,請參閱Mac mini 記憶體壓縮與 Swap 優化指南

為了更高上限,我該用 root 跑 OpenClaw 嗎?不該。請修正 plist;root 只會擴大爆炸半徑。

活動監視器夠用嗎?臨時查看可以,但夜間回歸需要自動化的 memory_pressure 或自訂取樣。

若我還跑了 Docker 邊車呢?請先把容器的 RSS 從七成預算裡扣掉,再替 OpenClaw 分配剩下的空間。

我該把 swap 完全關掉嗎?一般情況不建議。比較健康的做法是讓上層服務在記憶體不足時優雅降載,而不是假裝系統永遠不會換頁。

為何在資源吃緊的 OpenClaw 場景仍選 Apple Silicon Mac mini

M4 統一記憶體 讓模型權重與 macOS 服務共享頻寬時,較不容易遇到便宜 x86 虛擬機上那種 PCIe 與 NUMA 驚喜。透過 定價 按區域租用,也能讓財務在你要升到 24 GB SKU 以換取更高並行時,對應到明確的專案編號。原生 launchd 整合則讓「開機自啟、崩潰復原、資源上限」維持同一套敘事,而不是在 Linux 容器裡假裝自己很懂 Mac。最後,請把 plist 版本與 Git 提交並列紀錄,並依 GitOps 與設定版本化 連回部署流水線;工作結束就回收機器,讓資源上限與專案邊界一起關門。

為 OpenClaw 選對硬體規格

香港、日本、韓國、新加坡、美國 Mac mini,保留自動化需要的 headroom