2026:在租用的 Mac mini 上,用 launchd 為 OpenClaw 設定 ulimit、記憶體與 CPU 安全線
在 香港、日本、韓國、新加坡或美國 的 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,能顯著降低誤報。
把門檻寫成「比例加絕對值」的混合式通常最穩:比例用來吃掉不同 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,也請把「誰負責監看第一個探測成功」寫進輪值表,而不是假設自動化永遠會自我修復。
並行:先把 RSS 乘完,再決定要不要調高上限
並行代理指南 裡的每一條代理,都會複製模型控制代碼與 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 與設定版本化 連回部署流水線;工作結束就回收機器,讓資源上限與專案邊界一起關門。