Key takeaways
- Scheduled agents need always-on hardware, restartable processes, and auditable logs—hard on a sleeping laptop, natural on a remote Mac M4.
- launchd is macOS-native orchestration:
StartCalendarIntervalreplaces cron,KeepAlivekeeps daemons up,ThrottleIntervalstops crash loops. - MCP triggers should use curated tools plus an n8n webhook gateway—never expose every MCP port to the public internet.
- For parallel coding agents see our worktree parallel agent guide; for disk planning see runner disk & inode governance.
- Recommended path: daily PoC to prove launchd + first MCP job → weekly sprint lock → monthly once metrics hold.
- Message-style OpenClaw Gateway and batch CLI scheduled agents are different jobs—this post does not repeat port 18789 deployment.

1. Why put scheduled agents on a remote Mac in 2026
Cursor Automations, n8n MCP orchestration, and Codex CLI remote-control in 2026 turn "run an agent on a schedule" from a demo into billable workload. Execution still lives on macOS: launchd only schedules local processes, Seatbelt and Keychain block undeclared tool access, and local Macs lose jobs to sleep, Wi‑Fi handoffs, and update reboots.
Hosting a scheduled agent on a remote Mac M4 buys three things: ① 7×24 uptime with controlled restarts; ② lower RTT to LLM/MCP APIs (APAC or US East); ③ elastic daily/weekly/monthly leases so failed PoCs release without buying hardware.
This article focuses on scheduled/event triggers + MCP orchestration, not git worktree farms (covered elsewhere). OpenClaw gateway, port 18789 cold start, and tunnel+MCP appear only as boundary notes—not repeated here.
2. SSH provisioning and launchd/crnd environment: 30-minute checklist
After provisioning a remote Mac M4, finish the items below within 30 minutes before loading your first LaunchAgent. SSH details live in the help center.
- Key-based SSH login;
uname -mis arm64; timezone and NTP are correct. - Install Homebrew, Node 22 (common for agent CLIs), and
tmuxorscreenfor triage. - Create
~/agent-runs/state,~/agent-runs/logs,~/agent-runs/scripts. - Store API keys in
~/.config/agent/env(mode 600); inject via LaunchAgentEnvironmentVariables—never hard-code in the plist. - Run one manual
crnd scheduleor launchd test job and confirm logs land on disk. curlyour LLM/MCP endpoints from the machine and record RTT; confirm firewall rules for webhook callbacks.
Only then choose a PoC lease length. If any step fails, do not upgrade to weekly or monthly yet.
3. launchd LaunchAgent topology
User-level scheduled jobs belong in ~/Library/LaunchAgents/, loaded with launchctl bootstrap gui/$(id -u) (Ventura and later—prefer bootstrap over legacy load).
| Mode | Best for | Pros | Caveats |
|---|---|---|---|
| launchd direct script | Fixed intervals/calendar, single-repo batch | Zero deps, native, clear logs | Hand-write ThrottleInterval in plist |
| crnd / agent-reveille | Many agents, natural-language schedules | CLI-friendly, versionable | Still sits on launchd/cron |
| n8n + MCP webhook | Cross-system flows, human approval steps | Curated tools, retries, alerts | Extra hop latency; inbound security |
Typical LaunchAgent keys:
StartCalendarInterval: day/hour/minute triggers—crontab replacement.KeepAlive: restart on exit; pair withThrottleIntervalagainst crash loops.StandardOutPath/StandardErrorPath: mandatory—without logs you are guessing.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>Label</key><string>com.kvmboot.agent.codex-batch</string>
<key>ProgramArguments</key><array>
<string>/bin/zsh</string><string>-lc</string>
<string>source ~/.config/agent/env && ~/agent-runs/scripts/run-codex-batch.sh</string>
</array>
<key>StartCalendarInterval</key><dict>
<key>Minute</key><integer>0</integer><key>Hour</key><integer>*/1</integer>
</dict>
<key>ThrottleInterval</key><integer>300</integer>
<key>StandardOutPath</key><string>/Users/runner/agent-runs/logs/codex.out.log</string>
<key>StandardErrorPath</key><string>/Users/runner/agent-runs/logs/codex.err.log</string>
</dict></plist>Load: launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.kvmboot.agent.codex-batch.plist. Unload with bootout on the same path.
4. MCP / n8n webhook trigger chain and curated tools
MCP triggers usually take one of two paths: agent CLI connects directly to an MCP server (inside VPN or SSH tunnel), or n8n/Cursor Automations hits a webhook that runs scripts on the remote runner (when you need approval, retries, and alerts).
Security boundaries:
- Expose only curated tools—whitelisted file I/O and fixed APIs. Never put an unrestricted shell MCP on the public internet.
- Validate inbound webhooks with HMAC or short-lived tokens; write idempotency keys to
state/runs. - Hard timeouts on LLM calls; exponential backoff on failure to avoid launchd + webhook double-trigger storms.
2026 Mattermost + n8n + MCP case studies show orchestration in n8n and execution on macOS runners—compatible with launchd direct calls; pick based on whether you already run n8n.
5. Claude Code / Codex CLI scheduled task templates
Claude Code and Codex CLI both support non-interactive batch runs. Keep AGENTS.md, working directory, and env injection identical so launchd matches manual SSH results.
#!/bin/zsh set -euo pipefail cd ~/projects/my-repo export CODEX_API_KEY="$(grep CODEX_API_KEY ~/.config/agent/env | cut -d= -f2-)" /usr/local/bin/codex exec --prompt-file ~/agent-runs/prompts/nightly-review.md \ >> ~/agent-runs/logs/codex-$(date +%Y%m%d).log 2>&1
With agent-reveille / crnd: reveille add "0 9 * * 1-5" -- codex exec ... experiments quickly; export a plist into IaC once stable.
Pair with our worktree parallel agent workflow: scheduled jobs handle merge/review batches; interactive coding stays in worktrees to avoid directory lock contention.
6. Logs, secrets, and storage planning
Long-running agents fill state/runs, model caches, and npm/pnpm stores fast. Rule-of-thumb ranges (not SLA): lightweight copy agents may run weeks on 512 GB; local Ollama or heavy artifacts can hit 512 GB in 2–4 weeks.
- Rotate logs—daily splits or log rotation—so one file does not exhaust inodes.
- Secrets in Keychain or mode-600 env files only; never plaintext tokens in
ProgramArguments. - At 80% disk, purge run caches first, then scale storage or add a second parallel runner.
Memory: API-only agents often fit 16 GB; Ollama 7B+ or parallel Claude Code needs swap monitoring and memory peak governance—upgrade to 24 GB or split workloads.
7. Task frequency × lease: decision matrix and migration checklist
| Workload | Storage pressure | Suggested lease | Upgrade when |
|---|---|---|---|
| Hourly light MCP | Low (<5 GB/week) | Daily PoC → monthly | 7 clean days, auditable logs |
| Daily Codex full-repo review | Medium (10–30 GB/week) | Weekly → monthly | Disk <70%, stable RTT |
| Multi-agent + local models | High (>50 GB/week) | Weekly + 512GB→1TB | Consider second parallel runner |
Pricing per plans page and console. Sequence: daily PoC (1–3 days, launchd + first MCP job) → weekly iteration sprint → monthly after two stable weeks. PoC checklist:
- LaunchAgent loads and survives reboot.
- At least one webhook/MCP end-to-end success with idempotent state.
- 24h logs without uncaught panics; acceptable disk/inode growth.
- API RTT and error rate within team thresholds (pick APAC or US East for the workload).
8. Boundary with OpenClaw Gateway (contrast only)
OpenClaw excels at message gateways: public 18789, tunnel+webhook, multi-channel ingress. This article's scheduled agent is batch CLI—launchd triggers (no always-on HTTP port), ideal for cron-style jobs. Both can share one remote Mac M4 with separate accounts and directories. See the OpenClaw column for gateway deployment; not expanded here.
9. Triage FAQ
Plist won't load? Run plutil -lint; ensure Label is unique; on Ventura+ use bootstrap, not load.
launchctl bootstrap I/O error? bootout the same job first; confirm plist is under LaunchAgents and paths have no stray spaces.
Job runs but no logs? Check StandardOutPath parent exists and the run user can write.
KeepAlive restart loop? Increase ThrottleInterval; add traps before set -e; verify the script is not exiting instantly.
Seatbelt / Keychain blocking CLI? Authorize Keychain once over interactive SSH; LaunchAgent needs the same PATH and HOME as your login shell.
MCP timeouts? Cap tool count, raise timeout, move runner closer to the API region.
Duplicate webhook fires? Idempotency keys in state/runs; enable dedupe in n8n.
Ollama local inference OOM? Smaller model, limit concurrency, upgrade RAM or split to a second runner.
When to add a second parallel runner? Disk 80%+ after cleanup, or CPU >85% sustained with shardable tasks.
Minimum daily PoC length? Cover one full 24h schedule cycle plus one manual webhook trigger.
How does this differ from the worktree parallel article? This post owns when jobs run; worktree owns where and how parallel coding happens.
OpenClaw or launchd? Messages/gateways → OpenClaw; cron/batch → launchd. No SLA promises—validate with PoC measurements.
Run stable scheduled agents on cloud Mac mini
Dedicated M4, low idle power, and native macOS launchd fit Claude Code / Codex CLI chains better than Windows Task Scheduler or Linux cron. APAC and US East nodes, SSH out of the box, 7×24 unattended runs without depending on your laptop lid. Pass the PoC checklist above before locking lease length—total cost beats buying a workstation plus power and ops time.
Start with a daily lease in your target region to validate launchd + your first MCP trigger— compare lease tiers, check RAM and storage, read SSH access, or start from home; more guides on the blog.